Self-Hosting Guide: Deploy Cal.com for Scheduling
·OSSAlt Team
calcomschedulingself-hostingdockerguide
Self-Hosting Guide: Deploy Cal.com for Scheduling
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.
Requirements
- VPS with 2 GB RAM minimum
- Docker and Docker Compose
- Domain name (e.g.,
cal.yourdomain.com) - 10+ GB disk
- SMTP service for booking notifications
Step 1: Clone and Configure
git clone https://github.com/calcom/cal.com.git
cd cal.com
# Copy environment
cp .env.example .env
Step 2: Configure Environment
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)
Step 3: Docker Compose Setup
# 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:
Step 4: Start Cal.com
docker compose up -d
Step 5: Reverse Proxy (Caddy)
# /etc/caddy/Caddyfile
cal.yourdomain.com {
reverse_proxy localhost:3000
}
sudo systemctl restart caddy
Step 6: Initial Setup
- Open
https://cal.yourdomain.com - Create your admin account
- Set your timezone and availability
Step 7: Connect Calendars
Google Calendar:
- Go to Google Cloud Console
- Create a project and enable Calendar API
- Create OAuth 2.0 credentials
- Add
https://cal.yourdomain.com/api/integrations/googlecalendar/callbackas redirect URI - Add credentials to
.env
Microsoft Outlook:
- Go to Azure Portal
- Register an application
- Add Calendar.ReadWrite permissions
- Add
https://cal.yourdomain.com/api/integrations/office365calendar/callbackas redirect URI
Apple Calendar:
- Connect via CalDAV in Cal.com settings
Step 8: Create Event Types
| 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:
- Duration and buffer time
- Availability windows (e.g., Mon-Fri 9am-5pm)
- Minimum notice (e.g., 4 hours)
- Questions for invitees
- Confirmation email template
- Meeting location (Zoom, Google Meet, phone)
Step 9: Team Scheduling (Optional)
Set up team features:
- Settings → Teams → create a team
- Invite team members
- Create team event types:
- Round Robin — distributes bookings evenly across team
- Collective — requires all team members to be available
- Managed Event Types — admin controls event type settings for all members
Step 10: Embed on Your Website
<!-- 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>
Production Hardening
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:
- Monitor port 3000 with Uptime Kuma
- Set up alerts for failed email delivery
- Monitor PostgreSQL disk usage
Resource Usage
| 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 |
VPS Recommendations
| 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.