Self-hosting
Run Claril with Docker
Claril is self-hostable end to end. One docker compose up builds the Next.js app, starts PostgreSQL, applies the database schema, and serves the workbench. No proprietary cloud dependency is required.
CLARIL_ENCRYPTION_KEY to encrypt stored keys with a dedicated secret.What's in the box
The root docker-compose.yml defines three services:
db— PostgreSQL 17 (pinned), persisted to theclaril_pgdatavolume.migrate— a one-shot job that runs Drizzle migrations, then exits;webwaits for it.web— the Next.js standalone server, running as a non-root user in production mode.
The image is built from the multi-stage root Dockerfile: a builderstage installs the workspace with the committed lockfile and produces Next's output: "standalone" bundle; a runner stage serves it on minimal node:22-alpine; and a migrator stage carries @claril/db plus drizzle-kit for the migration job.
Quick start
# 1. Provide configuration (root .env — compose reads it automatically)
cp .env.example .env
# then set a real BETTER_AUTH_SECRET:
# openssl rand -base64 32
# 2. Build and start the whole stack
docker compose up -d --build
# 3. Open the app -> http://localhost:3000On first run the db volume is created, migrate applies the schema, and web starts once migration completes. Subsequent runs reuse the volume; migrate is idempotent (already-applied migrations are skipped).
Required configuration
These are read from the root .env (see .env.example). Secrets are never baked into the image — they are passed as runtime environment.
BETTER_AUTH_SECRET— required. Generate withopenssl rand -base64 32. Change it for any real deployment.BETTER_AUTH_URL— the externally reachable base URL of the app (defaulthttp://localhost:3000).DATABASE_URL— defaults to the bundleddbservice (postgresql://claril:claril@db:5432/claril). Set this to use external Postgres.POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB— credentials for the bundled database (defaultclaril).POSTGRES_PORT/WEB_PORT— host ports (default5432/3000).CLARIL_ENCRYPTION_KEY— optional. Used to encrypt stored BYOK AI keys (AES-256-GCM). If unset, Claril falls back toBETTER_AUTH_SECRET. Set a dedicated value if you want to rotate it independently of the auth secret.
Using an external Postgres (e.g. Neon)
Claril depends only on a standard DATABASE_URL; any vanilla Postgres works. To skip the bundled database:
- Set
DATABASE_URLin.envto your external connection string (include?sslmode=requirefor managed providers like Neon). - Start only the migration job and the app:
docker compose up -d --build migrate webThe db service is simply left unused.
Running migrations
The migrate service runs drizzle-kit migrate automatically before web starts. To run it on demand (e.g. after pulling a new image with new migrations):
docker compose run --rm migrateOutside Docker (local development), apply the schema directly with the workspace script:
pnpm --filter @claril/db db:migrateMigrations are committed to @claril/db (currently through 0011) and applied in order. After pulling a new image or release, run the migrate step again to pick up any new ones.
Operations
docker compose logs -f web # tail the app logs
docker compose ps # service / health status
docker compose down # stop (keeps the data volume)
docker compose down -v # stop AND delete the Postgres volume (data loss)docker compose down -v deletes the Postgres volume and all stored diagrams. Use plain down to stop without losing data.Notes
- Base images are pinned (Node and Postgres) — no bare latest tags.
- The runtime container runs as a non-root user with
NODE_ENV=production. - Licensed AGPL-3.0-only; the bundled Postgres is upstream / vanilla.
Next: lint your models outside the app with the CLI & MCP.