Cal.com is the leading open-source scheduling platform — a self-hostable Calendly alternative built on Next.js and Prisma. Self-hosting gives you data ownership, custom branding, and no per-seat fees. The catch is that Cal.com is famously environment-variable heavy and requires database migrations, so a careful setup pays off.
This guide deploys Cal.com with a managed PostgreSQL database.
What Cal.com needs
| Component | Role |
|---|---|
| Next.js app | Web UI + API |
| PostgreSQL | Bookings, users, event types, availability |
| Calendar integrations | Google/Office365 for two-way sync |
| Email (SMTP) | Booking confirmations and notifications |
Cal.com is primarily one web application plus a database. Most of the work is configuration.
Step 1: Provision managed PostgreSQL
On [PandaStack](https://dashboard.pandastack.io), create a managed PostgreSQL (14.x or 16.x). When linked, DATABASE_URL is injected — Cal.com (via Prisma) reads DATABASE_URL directly, so this maps cleanly with no manual string-building.
Step 2: Generate required secrets
Cal.com needs an encryption key and a NextAuth secret:
openssl rand -base64 32 # NEXTAUTH_SECRET
openssl rand -base64 24 # CALENDSO_ENCRYPTION_KEY (used to encrypt stored credentials)CALENDSO_ENCRYPTION_KEY encrypts third-party calendar tokens at rest — keep it stable and secret, or stored integrations become unreadable.
Step 3: Assemble the core environment
Cal.com has many variables; here are the essentials:
NEXT_PUBLIC_WEBAPP_URL=https://cal.example.com
NEXTAUTH_URL=https://cal.example.com
NEXTAUTH_SECRET=<generated>
CALENDSO_ENCRYPTION_KEY=<generated>
DATABASE_URL=${DATABASE_URL} # injected by linking managed PostgreSQL
DATABASE_DIRECT_URL=${DATABASE_URL} # Prisma direct connection for migrations
# Email
EMAIL_FROM=notifications@example.com
EMAIL_SERVER_HOST=<smtp-host>
EMAIL_SERVER_PORT=587
EMAIL_SERVER_USER=<user>
EMAIL_SERVER_PASSWORD=<password>The URL variables must all match your real HTTPS domain — mismatches break authentication and booking links.
Step 4: Run Prisma migrations
Cal.com's schema is managed by Prisma. Run migrations before first boot as a pre-deploy step using the Cal.com image:
npx prisma migrate deployThis applies all pending migrations to your managed PostgreSQL. Run it on every deploy that ships schema changes.
Step 5: Deploy the container
Use the official self-hosting image (or build from the repo's Dockerfile):
FROM calcom/cal.com:latest
# Serves on port 3000Pin a version in production.
- 1Push the repo (or reference the image) to GitHub.
- 2Create a container app on PandaStack.
- 3Link the managed PostgreSQL so
DATABASE_URLis injected. - 4Set all env vars (secrets as secrets).
- 5Set
npx prisma migrate deployas the pre-deploy hook. - 6Expose port
3000. - 7Add a custom domain matching your URL vars; SSL is automatic.
Build note: Cal.com bakes some
NEXT_PUBLIC_*values at build time. If you build from source rather than the prebuilt image, ensureNEXT_PUBLIC_WEBAPP_URLis set during the build, not just at runtime.
Step 6: Connect calendar integrations
The whole point of a scheduler is two-way calendar sync. For Google Calendar:
- 1Create OAuth credentials in Google Cloud Console.
- 2Add the redirect URI
https://cal.example.com/api/integrations/googlecalendar/callback. - 3Provide the credentials to Cal.com (via the
GOOGLE_API_CREDENTIALSenv var as a JSON blob).
For Office 365 / Outlook, register an app in Azure and supply the client ID/secret. Each integration has its own variables — add only the ones you need.
Step 7: First-run setup
Visit your domain and complete onboarding:
- 1Create the first admin user.
- 2Set availability and timezone.
- 3Connect your calendar (using the integration configured above).
- 4Create event types (e.g. "30-min intro call").
- 5Share your booking link.
Cal.com vs. Calendly, fairly
Calendly is polished, zero-ops, and has a deep integration marketplace — for many teams it's the path of least resistance. Cal.com's advantages are self-hosting (data residency, GDPR control), open-source extensibility, custom theming, and no per-seat pricing at scale. The tradeoff is operational ownership and a config-heavy setup. Both are strong; self-host when control and cost-at-scale matter.
Operating tips
- Back up PostgreSQL — it holds bookings and encrypted integration tokens. Scheduled backups cover it.
- Keep
CALENDSO_ENCRYPTION_KEYstable — losing it means re-connecting all calendars. - Match all URL env vars to the real domain.
- Pin image versions, run
prisma migrate deployon upgrades, and read release notes.
References
- [Cal.com documentation](https://cal.com/docs)
- [Cal.com self-hosting guide](https://cal.com/docs/introduction/quick-start/self-hosting)
- [Cal.com environment variables (.env.example)](https://github.com/calcom/cal.com/blob/main/.env.example)
- [Prisma Migrate deploy](https://www.prisma.io/docs/orm/prisma-migrate/workflows/development-and-production)
---
Cal.com is mostly a careful env-and-migrations exercise on top of a Next.js app and a database — and PandaStack streamlines the hard parts: managed PostgreSQL with an injected DATABASE_URL, pre-deploy migration hooks, and automatic SSL on your domain. Self-host your scheduling at [dashboard.pandastack.io](https://dashboard.pandastack.io).