Booking data reveals who meets whom and when, which is reason enough to keep it in-house for anyone scheduling with patients, clients or candidates. The application itself is a compose file; the work is registering your instance with each calendar provider and getting two settings right that cannot be changed later without a rebuild. This covers both, plus the hour-off bug that always appears.
Scheduling looks trivial until you consider what a booking page must know: your real availability across several calendars, in the visitor's timezone, without offering a slot twice to two people who load the page simultaneously. Self-hosting Cal.com means owning that, plus the OAuth relationships that make calendars readable at all.
The OAuth Work Is the Real Setup
Installing the application is a Docker Compose file. Making it useful requires registering your instance as an OAuth application with each calendar provider you intend to connect, and that is the part that takes an afternoon.
For Google, that means a Cloud project, the Calendar API enabled, an OAuth consent screen, and a client with your instance's callback URL as an authorised redirect. For Microsoft, an Entra app registration with calendar permissions and the same callback. Neither is difficult and neither is optional: without them, the calendar integration list is present and every entry fails.
Two things to get right the first time. The redirect URI must match your instance's public URL exactly, scheme and trailing path included, or the provider rejects the handshake with an error that names nothing useful. And a Google consent screen left in testing mode only works for accounts you list explicitly, which is fine for personal use and confusing when a colleague cannot connect.
Deploy It
sudo apt update && sudo apt install -y docker.io docker-compose-plugin git
git clone --recursive https://github.com/calcom/docker.git /opt/calcom
cd /opt/calcom && cp .env.example .env
openssl rand -base64 32 # NEXTAUTH_SECRET
openssl rand -hex 32 # CALENDSO_ENCRYPTION_KEY
sudo docker compose up -d
sudo docker compose logs -f calcom
Note --recursive: the repository uses a submodule for the application itself, and a clone without it produces a build that fails in a way that does not mention submodules.
Two secrets in that .env deserve attention beyond generating them.
CALENDSO_ENCRYPTION_KEY encrypts the stored OAuth tokens and credentials. Lose it and every calendar connection is unreadable; every user reconnects. It is a credential, and it belongs in your backups as one.
NEXT_PUBLIC_WEBAPP_URL is the external URL and is compiled into the frontend at build time rather than read at runtime. Changing it means rebuilding, and setting it wrong produces a working-looking app whose booking links point somewhere else.
Sizing
| Scale | vCPU / RAM / SSD | Monthly on per-resource pricing |
|---|---|---|
| Individual or small team | 2 / 4 GB / 40 GB | $9.34 |
| Team of 10-50 | 4 / 8 GB / 80 GB | $18.68 |
| Company-wide, heavy booking traffic | 8 / 16 GB / 160 GB | $37.36 |
Those use MassiveGRID's per-resource rates of $2.87 per CPU core, $0.80 per GB of RAM and $0.01 per GB of SSD per month, less 20% annually. The data itself is small, so storage barely matters; memory does, because this is a Next.js application and the build is the memory-hungry part. A 2 GB instance can run it and cannot build it.
Timezones Are Where Scheduling Goes Wrong
Every scheduling bug anyone reports is a timezone bug, and there are three separate clocks involved: the host's, the user's stated timezone, and the visitor's browser.
Set the server to UTC and leave it there. Then verify one specific case before trusting the instance: a booking made by a visitor in a timezone that is currently in daylight saving, for a slot in a week when either party's offset changes. That is the case that breaks, and it breaks by an hour, which is exactly the error nobody notices until somebody misses a meeting.
The other habit worth adopting is checking availability against the actual calendar rather than the app's cached view. A busy event created directly in Google after Cal.com last synced can produce a double booking, and the sync interval is what determines your exposure.
The Features That Need More Than the App
Three commonly wanted capabilities depend on services outside the instance, and finding that out after promising them internally is awkward.
Video links. Automatic conferencing links need an integration with the provider. Self-hosted alternatives connect too, which for a team that already runs its own meeting server is the tidier arrangement.
Payments. Charging for bookings needs a payment provider account and its keys. The instance handles the flow; the money is somebody else's system.
Email. Not optional. Confirmations, reminders and cancellations are the product, and a booking system whose mail lands in spam is worse than no booking system. Use an authenticated relay with SPF, DKIM and DMARC on the sending domain, and test to an external address before rolling it out.
What to Back Up
docker compose exec -T database \
pg_dump -U unicorn_user calendso | gzip > /backup/calcom-$(date +%F).sql.gz
cp /opt/calcom/.env /backup/calcom-env-$(date +%F)
Both, and the second is the one people omit. The database holds bookings and connected-calendar records; the encryption key in .env is what makes those records readable. A restore with the database and without the key gives you a booking history and no working calendar connections.
Send the copies off the instance. Backup services use block-level incremental backups with AES-256 encryption at $0.01 per GB, and our guide to encryption keys and recovery covers custody of the kind of key this is.
Upgrades and the Build Step
This is a fast-moving project, and upgrades involve a container rebuild plus database migrations. Two practical consequences.
Pin the image tag rather than tracking latest, so a container recreated after a reboot does not silently upgrade itself at a moment you did not choose. And rehearse on a restored copy before upgrading an instance a team depends on, because a booking page that goes down takes your inbound meetings with it. Our guide to upgrading self-hosted applications covers the pattern.
What You Gain, and What You Take On
You gain booking data on your own infrastructure, which for anyone scheduling with patients, clients or applicants is the whole reason to do this, since a hosted scheduler sees who meets whom and when. No per-seat cost as the team grows, and placement in a jurisdiction you chose.
You take on the OAuth relationships, the timezone edge cases, the mail deliverability, and an upgrade cadence. For a small team that is a modest ongoing cost; for one person scheduling occasional calls it may not be worth it, and that is a fair conclusion. Our guide to sizing self-hosted applications covers running this alongside other small services on one machine, which is how the arithmetic usually works out.
Infrastructure for a Booking Page
A booking page is public, is the first thing a prospect touches, and is useless while it is down.
MassiveGRID runs Proxmox high-availability clustering with automatic failover over Ceph storage replicating every block three times across independent NVMe drives, behind a 100% uptime SLA, with always-on DDoS mitigation in front. A Linux VPS at the rates above suits every scale above, and instances can be ordered across a partner footprint of more than 700 datacenters in 85 metros, 30 countries and six continents, with auto-provisioning in New York, London, Frankfurt and Singapore, so the page loads quickly for the people you want booking meetings.