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.

The deterministic features (logic inspector, versioning, catalog, comments, export) work with no AI key configured. AI is BYOK and configured at runtime in each personal or organization space, so there is nothing AI-related you must set in the environment to get started — though you can set 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 the claril_pgdata volume.
  • migrate — a one-shot job that runs Drizzle migrations, then exits; web waits 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

one-command stack
# 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:3000

On 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_SECRETrequired. Generate with openssl rand -base64 32. Change it for any real deployment.
  • BETTER_AUTH_URL — the externally reachable base URL of the app (default http://localhost:3000).
  • DATABASE_URL — defaults to the bundled db service (postgresql://claril:claril@db:5432/claril). Set this to use external Postgres.
  • POSTGRES_USER / POSTGRES_PASSWORD / POSTGRES_DB — credentials for the bundled database (default claril).
  • POSTGRES_PORT / WEB_PORT — host ports (default 5432 / 3000).
  • CLARIL_ENCRYPTION_KEYoptional. Used to encrypt stored BYOK AI keys (AES-256-GCM). If unset, Claril falls back to BETTER_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:

  1. Set DATABASE_URL in .env to your external connection string (include ?sslmode=require for managed providers like Neon).
  2. Start only the migration job and the app:
docker compose up -d --build migrate web

The 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 migrate

Outside Docker (local development), apply the schema directly with the workspace script:

pnpm --filter @claril/db db:migrate

Migrations 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

common commands
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.