RoyceSelf-Hosting Guide: Deploy Cal.com for Scheduling Cal.com is the open source Calendly...
Cal.com is the open source Calendly alternative. Self-hosting gives you unlimited booking pages, unlimited event types, and team scheduling — all without per-user pricing.
cal.yourdomain.com)git clone https://github.com/calcom/cal.com.git
cd cal.com
# Copy environment
cp .env.example .env
Edit .env:
# App
NEXT_PUBLIC_WEBAPP_URL=https://cal.yourdomain.com
NEXTAUTH_SECRET=your-random-secret-min-32-chars
CALENDSO_ENCRYPTION_KEY=your-random-encryption-key-32-chars
# Database
DATABASE_URL=postgresql://calcom:your-strong-password@db:5432/calcom
DATABASE_DIRECT_URL=postgresql://calcom:your-strong-password@db:5432/calcom
# Email
EMAIL_FROM=cal@yourdomain.com
EMAIL_SERVER_HOST=smtp.resend.com
EMAIL_SERVER_PORT=587
EMAIL_SERVER_USER=resend
EMAIL_SERVER_PASSWORD=re_your_api_key
# Calendar integrations (get from Google Cloud Console)
GOOGLE_API_CREDENTIALS={"client_id":"...","client_secret":"..."}
# Microsoft Calendar (get from Azure Portal)
# MS_GRAPH_CLIENT_ID=your-client-id
# MS_GRAPH_CLIENT_SECRET=your-client-secret
Generate secrets:
openssl rand -hex 32 # NEXTAUTH_SECRET
openssl rand -hex 16 # CALENDSO_ENCRYPTION_KEY (must be 32 chars)
# docker-compose.yml
services:
calcom:
image: calcom/cal.com:latest
container_name: calcom
restart: unless-stopped
ports:
- "3000:3000"
env_file: .env
depends_on:
- db
db:
image: postgres:16-alpine
container_name: calcom-db
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_DB=calcom
- POSTGRES_USER=calcom
- POSTGRES_PASSWORD=your-strong-password
volumes:
postgres_data:
docker compose up -d
# /etc/caddy/Caddyfile
cal.yourdomain.com {
reverse_proxy localhost:3000
}
sudo systemctl restart caddy
https://cal.yourdomain.com
Google Calendar:
https://cal.yourdomain.com/api/integrations/googlecalendar/callback as redirect URI.env
Microsoft Outlook:
https://cal.yourdomain.com/api/integrations/office365calendar/callback as redirect URIApple Calendar:
| Event Type | Duration | Use Case |
|---|---|---|
| Quick Chat | 15 min | Initial calls, quick questions |
| Discovery Call | 30 min | Sales conversations |
| Team Meeting | 30 min | Internal sync |
| Deep Dive | 60 min | Technical discussions |
| Pair Programming | 90 min | Collaborative coding |
For each event type, configure:
Set up team features:
<!-- Inline embed -->
<iframe
src="https://cal.yourdomain.com/your-username/30min"
width="100%"
height="700"
frameborder="0"
></iframe>
<!-- Or use Cal.com embed snippet -->
<script>
(function (C, A, L) {
let p = function (a, ar) { a.q.push(ar); };
let d = C.document;
C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments;
if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A;
cal.loaded = true; }
if (ar[0] === L) { const api = function () { p(api, arguments); };
const namespace = ar[1]; api.q = api.q || [];
typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar);
return; }
p(cal, ar);
};
})(window, "https://cal.yourdomain.com/embed/embed.js", "init");
Cal("init");
Cal("ui", {"styles":{"branding":{"brandColor":"#000000"}}});
</script>
Backups:
# Database backup (daily cron)
docker exec calcom-db pg_dump -U calcom calcom > /backups/calcom-$(date +%Y%m%d).sql
Updates:
docker compose pull
docker compose up -d
Monitoring:
| Users | RAM | CPU | Disk |
|---|---|---|---|
| 1-10 | 2 GB | 2 cores | 10 GB |
| 10-50 | 4 GB | 2 cores | 20 GB |
| 50+ | 8 GB | 4 cores | 30 GB |
| Provider | Spec | Price |
|---|---|---|
| Hetzner | 2 vCPU, 4 GB RAM | €4.50/month |
| DigitalOcean | 2 vCPU, 2 GB RAM | $12/month |
| Linode | 1 vCPU, 2 GB RAM | $12/month |
Compare scheduling tools on OSSAlt — features, integrations, and pricing side by side.