Cal.com 6.16.1 self-host: the migrations don't run on boot

The official calcom/cal.com:latest image starts a Next.js app, listens on port 3000, and dutifully redirects you from / to /auth/login. What it does not do is run prisma migrate deploy against the Postgres you wired up. The container ends up in a fast crash loop with the same Prisma error in every log line, and the only signal in the browser is a generic Next.js 500. Here's the exact log, the two-step fix, and the resource numbers from a real install.

Honest about what this is

We're the team behind SimpleReview, a Chrome extension that drafts code-fix PRs on whatever site you click. Not affiliated with Cal.com. This page is a deployment note from one real install on one Linux box, dated above. If we got something wrong, open a GitHub issue and we'll fix it.

The exact docker run we used

# 1. Postgres sidecar
docker run -d --name calpg \
  -e POSTGRES_USER=cal \
  -e POSTGRES_PASSWORD=cal \
  -e POSTGRES_DB=calendso \
  postgres:15-alpine

# 2. The app — what the README more or less suggests
docker run -d --name caltest -p 18081:3000 \
  --link calpg:db \
  -e DATABASE_URL="postgresql://cal:cal@db:5432/calendso" \
  -e NEXTAUTH_URL="http://localhost:18081" \
  -e NEXT_PUBLIC_WEBAPP_URL="http://localhost:18081" \
  -e NEXTAUTH_SECRET="$(openssl rand -base64 32)" \
  -e CALENDSO_ENCRYPTION_KEY="$(openssl rand -base64 24)" \
  calcom/cal.com:latest

The container starts, listens on 3000, and HTTP-307 redirects / to /auth/login. So far so good — except that Postgres is a fresh DB with no schema, and the image never ran a migration.

What "/" does when there's no schema

Hitting any page produces this log spam in the container:

@calcom/web:start: Invalid `prisma.user.findFirst()` invocation:
@calcom/web:start:
@calcom/web:start: The table `public.users` does not exist in the current database.
@calcom/web:start:     at async m (.next/server/chunks/ssr/...)
@calcom/web:start: Error [PrismaClientKnownRequestError]:
@calcom/web:start:   code: 'P2021',
@calcom/web:start:   meta: [Object],
@calcom/web:start:   clientVersion: '6.16.1',
Broken state — observed locally

The calcom/cal.com:latest image (digest sha256:ace3bb1219fb… as of 2026-05-07) does not include a startup hook that runs prisma migrate deploy. It boots the Next.js production server immediately. Every page-load that tries to look up a user (which is to say, every page that isn't a static asset) hits Prisma error P2021 — Table 'public.users' does not exist.

The browser sees a generic Next.js 500. The container is technically "Up" and the docker-level healthcheck (if you wired one) reports green. The actual bug is invisible unless you tail the container logs.

Step 1 — run the migrations manually

Prisma 6 wants both DATABASE_URL (pooled) and DATABASE_DIRECT_URL (unpooled, for migrations). The Cal.com schema sets directUrl = env("DATABASE_DIRECT_URL"), so omitting it makes prisma migrate deploy fail validation:

Error: Prisma schema validation
error: Environment variable not found: DATABASE_DIRECT_URL.
  --> packages/prisma/schema.prisma:7
   |
 6 |   url       = env("DATABASE_URL")
 7 |   directUrl = env("DATABASE_DIRECT_URL")

Set it (the same connection string is fine for a single-Postgres self-host) and run the migration from inside the running container:

docker exec \
  -e DATABASE_DIRECT_URL="postgresql://cal:cal@db:5432/calendso" \
  caltest \
  sh -c "cd /calcom && npx -y prisma migrate deploy --schema=packages/prisma/schema.prisma"

This applied just over three hundred migrations end-to-end on the empty database — every schema change Cal.com has shipped from 2022 to mid-2026. Took about 60 seconds on a fast NVMe Postgres. The final line is a clean All migrations have been successfully applied.

Step 2 — restart the app

Prisma's client caches the introspected schema at process start. Even after the migration, the running Next.js server keeps throwing P2022 — column does not exist until you restart it:

docker restart caltest
Fix summary

1. Run the migration with both DATABASE_URL and DATABASE_DIRECT_URL set.

2. Restart the app container so Prisma re-introspects the new schema.

For unattended deploys, wrap these into your image's ENTRYPOINT with something like npx prisma migrate deploy && npm run start, or use a sidecar init container.

After the restart, /auth/setup?step=1 renders the three-step Administrator user wizard:

Cal.com Administrator user setup — Step 1 of 3 — username, full name, email, password fields
Cal.com 6.16.1 first-run wizard, step 1 of 3, after migrations were applied. The username field's placeholder shows your NEXT_PUBLIC_WEBAPP_URL as a slug prefix — quick way to verify that env var is right.

Resource numbers

PhaseWall clockNotes
docker run → first 307~30 sImage already pulled, Postgres already healthy
prisma migrate deploy~60 sEmpty DB → ~300 migrations applied
Container restart → wizard~25 sNext.js cold start once schema is current
App RAM at idle1.13 GiBAfter wizard renders, no users created yet
Postgres RAM at idle50 MiBFresh schema, no traffic
Container disk write~350 MBMostly Next.js trace cache + node modules in .next

So the true cold-start time on a clean machine is around two minutes end-to-end, dominated by the migration step. Plan for it in your deploy pipeline; it's not a thirty-second job.

Quiet warnings worth knowing about

Even after the wizard renders, the container log throws three warnings on every restart that don't surface in the UI:

  • EMAIL_FROM environment variable is not set, this may indicate mailing is currently disabled. — true, and it means password-reset, booking confirmation, and invites all silently no-op. Set EMAIL_FROM + an SMTP provider before sharing the URL with anyone who'll book a meeting.
  • [Phase: phase-production-server] Skipping rewrite config for organizations because ORGANIZATIONS_ENABLED is not set — fine if you don't need teams, but the multi-tenant feature is a hidden flag.
  • [WebPush] Missing VAPID keys. Web push notifications are disabled. — Cal.com supports browser push reminders; without VAPID keys you won't get them. npx web-push generate-vapid-keys, then set the public/private pair in env.

Things we'd change in the docker README

  1. Bake migrate-on-boot into the image. An entrypoint.sh that runs prisma migrate deploy on first start — and is a no-op when there's nothing to migrate — would eliminate the most common self-host failure mode.
  2. Document DATABASE_DIRECT_URL. The schema requires it; the README sample .env only shows DATABASE_URL. Most users find out the hard way.
  3. Surface env warnings on the login screen. A small "running with degraded features (mail disabled, push disabled)" banner during initial setup would catch the silent footguns above.
  4. Provide a one-shot Compose example with both containers, network, and volume. The current docs link to a separate calcom/docker repo whose state lags cal.com main; a self-contained compose.yaml in the main repo would beat any external example.

Where this fits

One short, honest write-up per self-hostable booking/scheduling/CMS tool we actually run on a real Linux box. Adjacent: Dify 1.14.0 — the profile flag the README forgets, Open WebUI on Linux Docker — first 90 seconds, measured. SimpleReview is the Chrome extension that turns whatever element you click on a broken admin into a draft code-fix PR — Cal.com setup wizards included.

Demo: SimpleReview on the Cal.com setup wizard

localhost:18081/auth/setup
SimpleReview
Cal.com · Administrator user
Let's create the first administrator user.
Step 1 of 3
Username
http://localhost:18081/
Email address
⨯ Error [PrismaClientKnownRequestError]
Invalid `prisma.user.findFirst()` invocation:
The table `public.users` does not exist
code: 'P2021' · clientVersion: '6.16.1'
✓ Migrations applied · 312 of 312
$ docker exec -e DATABASE_DIRECT_URL=$DB caltest \\
   npx prisma migrate deploy
All migrations have been successfully applied.
$ docker restart caltest
→ /auth/setup?step=1 · 200 OK · wizard rendered
Comment×
migrate not run on boot|
Fix it ✓ Done
waiting for selection…
Detected
Prisma codeP2021
Causeno migrate
Fix plan
Run prisma migrate deploy with DATABASE_DIRECT_URL, then restart
Result
312 migrations applied. /auth/setup wizard renders.
✓ Fix ready
feat(docker): migrate-on-boot ENTRYPOINT
1 file · entrypoint.sh
Click SimpleReview → select the P2021 error → Fix it → migrations applied + restart