Cal.com uses PostgreSQL as its primary database. This guide covers database setup, migrations, connection pooling, and optimization.
Requirements
PostgreSQL : Version 13 or higher
Storage : Minimum 20GB, recommended 100GB+ for production
Memory : Minimum 2GB RAM allocated to PostgreSQL
Connection Limit : Adjust based on your deployment scale
Database Setup
Local PostgreSQL Installation
Ubuntu/Debian
# Install PostgreSQL
sudo apt-get update
sudo apt-get install postgresql postgresql-contrib
# Start PostgreSQL service
sudo systemctl start postgresql
sudo systemctl enable postgresql
macOS
# Using Homebrew
brew install postgresql@15
brew services start postgresql@15
Windows
Download from postgresql.org and follow the installer.
Create Database
# Connect to PostgreSQL
sudo -u postgres psql
# Create database and user
CREATE DATABASE calendso ;
CREATE USER calcom_user WITH ENCRYPTED PASSWORD 'your_secure_password' ;
GRANT ALL PRIVILEGES ON DATABASE calendso TO calcom_user ;
# Grant schema permissions (PostgreSQL 15+)
\c calendso
GRANT ALL ON SCHEMA public TO calcom_user ;
\q
PostgreSQL connection strings follow this format:
postgresql://[user]:[password]@[host]:[port]/[database]?[parameters]
Examples:
# Local database
DATABASE_URL = "postgresql://calcom_user:password@localhost:5432/calendso"
# Remote with SSL
DATABASE_URL = "postgresql://user:[email protected] :5432/calendso?sslmode=require"
# With connection pooling
DATABASE_URL = "postgresql://user:[email protected] :6543/calendso?pgbouncer=true"
# Self-signed SSL (Heroku, etc.)
DATABASE_URL = "postgresql://user:pass@host:5432/db?sslmode=no-verify"
Environment Configuration
Add to your .env file:
# Primary database connection
DATABASE_URL = "postgresql://calcom_user:password@localhost:5432/calendso"
# Direct connection for migrations (same as DATABASE_URL if not using pooling)
DATABASE_DIRECT_URL = "postgresql://calcom_user:password@localhost:5432/calendso"
# Optional: Separate analytics database
INSIGHTS_DATABASE_URL = "postgresql://user:pass@host:5432/calendso_insights"
# Optional: SAML database (Enterprise)
SAML_DATABASE_URL = "postgresql://user:pass@host:5432/calendso_saml"
Migrations
Cal.com uses Prisma for database schema management and migrations.
Initial Setup
For a new database, run migrations to create all tables:
# Development (includes seeding)
yarn workspace @calcom/prisma db-migrate
# Production (no seeding)
yarn workspace @calcom/prisma db-deploy
Migration Commands
Development
Production
Docker
# Apply pending migrations and generate Prisma Client
yarn workspace @calcom/prisma db-migrate
# Create a new migration
yarn workspace @calcom/prisma db-migrate-create "migration_name"
# Reset database (⚠️ deletes all data)
yarn workspace @calcom/prisma db-reset
Migration Best Practices
Always backup your database before running migrations in production
Test migrations in staging environment first
Use db-deploy (not db-migrate) in production
Never run db-reset in production (destroys all data)
The Docker start.sh script automatically runs migrations on container startup: npx prisma migrate deploy --schema /calcom/packages/prisma/schema.prisma
Schema Location
The Prisma schema is located at:
packages/prisma/schema.prisma
Migration Directory
Migration files are stored in:
packages/prisma/migrations/
Connection Pooling
Why Use Connection Pooling?
Connection poolers like PgBouncer help:
Reduce database connection overhead
Handle high-concurrency workloads
Manage connection limits efficiently
Improve application performance
PgBouncer Setup
Using Docker
services :
pgbouncer :
image : pgbouncer/pgbouncer:latest
environment :
- DATABASES_HOST=database
- DATABASES_PORT=5432
- DATABASES_USER=calcom_user
- DATABASES_PASSWORD=password
- DATABASES_DBNAME=calendso
- PGBOUNCER_POOL_MODE=transaction
- PGBOUNCER_MAX_CLIENT_CONN=1000
- PGBOUNCER_DEFAULT_POOL_SIZE=25
ports :
- "6432:6432"
depends_on :
- database
Configuration
Update your .env to use PgBouncer:
# Application connects through PgBouncer
DATABASE_URL = "postgresql://calcom_user:password@pgbouncer:6432/calendso?pgbouncer=true"
# Migrations bypass PgBouncer (direct connection required)
DATABASE_DIRECT_URL = "postgresql://calcom_user:password@database:5432/calendso"
DATABASE_DIRECT_URL must point to the actual database (not pooler) for migrations to work correctly.
Managed Pooling Services
Many providers offer built-in connection pooling:
Supabase
# Pooled connection (port 6543)
DATABASE_URL = "postgresql://postgres:[email protected] :6543/postgres"
# Direct connection (port 5432)
DATABASE_DIRECT_URL = "postgresql://postgres:[email protected] :5432/postgres"
Railway
Railway automatically provides both pooled and direct connections.
Neon
# Pooled endpoint
DATABASE_URL = "postgresql://user:[email protected] /db?sslmode=require"
# Direct endpoint
DATABASE_DIRECT_URL = "postgresql://user:[email protected] /db?sslmode=require"
Managed Database Providers
Railway
Create a new PostgreSQL database in Railway
Copy the connection string from Railway dashboard
Add to .env:
Railway PostgreSQL Guide
Render
Create a new PostgreSQL database in Render
Get external connection string
Configure:
Render PostgreSQL Docs
Supabase
Create a new Supabase project
Get connection strings from Settings > Database
Use pooled connection for app, direct for migrations:
Neon
Create a Neon project
Get pooled and direct connection strings
Configure:
Neon Quickstart
Amazon RDS
Create RDS PostgreSQL instance
Configure security groups for access
Get endpoint from RDS console:
SSL Configuration
Requiring SSL
DATABASE_URL = "postgresql://user:pass@host:5432/db?sslmode=require"
Self-Signed Certificates
For platforms like Heroku with self-signed certs:
PGSSLMODE = "no-verify"
DATABASE_URL = "postgresql://user:pass@host:5432/db"
Only use sslmode=no-verify when you trust the network and database provider.
SSL Modes
disable: No SSL
prefer: Try SSL, fallback to non-SSL
require: Require SSL, but don’t verify certificates
verify-ca: Require SSL and verify certificate
verify-full: Require SSL, verify certificate and hostname
Database Optimization
Indexes
Cal.com’s schema includes optimized indexes. Monitor slow queries:
-- Enable query logging
ALTER DATABASE calendso SET log_min_duration_statement = 1000 ;
-- Find slow queries
SELECT query, calls, total_time, mean_time
FROM pg_stat_statements
ORDER BY mean_time DESC
LIMIT 10 ;
Connection Limits
Adjust PostgreSQL connection limits in postgresql.conf:
max_connections = 100
shared_buffers = 256MB
effective_cache_size = 1GB
work_mem = 4MB
maintenance_work_mem = 64MB
Vacuum and Analyze
Enable auto-vacuum for maintenance:
-- Check auto-vacuum settings
SHOW autovacuum;
-- Manual vacuum analyze
VACUUM ANALYZE;
Backup Strategies
pg_dump Backup
# Backup database
pg_dump -h localhost -U calcom_user -d calendso -F c -f backup_ $( date +%Y%m%d ) .dump
# Restore database
pg_restore -h localhost -U calcom_user -d calendso -c backup_20260304.dump
Continuous Archiving (WAL)
For point-in-time recovery, enable WAL archiving:
wal_level = replica
archive_mode = on
archive_command = 'cp %p /path/to/archive/%f'
Managed Backups
Most managed providers offer automatic backups:
Supabase : Daily backups with point-in-time recovery
Railway : Automatic daily backups
Render : Daily backups included
RDS : Automated backups with configurable retention
Prisma Studio
Visual database browser included with Cal.com:
# Start Prisma Studio
yarn db-studio
# Access at http://localhost:5555
Prisma Studio is available in the Docker Compose setup at port 5555. Remove this service in production.
pgAdmin
Full-featured PostgreSQL management:
docker run -p 5050:80 \
-e [email protected] \
-e PGADMIN_DEFAULT_PASSWORD=admin \
dpage/pgadmin4
psql CLI
Connect directly to your database:
# Local connection
psql -U calcom_user -d calendso
# Remote connection
psql "postgresql://user:pass@host:5432/db?sslmode=require"
# Common commands
\dt # List tables
\d+ table # Describe table
\l # List databases
\conninfo # Connection info
Docker Compose Database
The default docker-compose.yml includes PostgreSQL:
services :
database :
container_name : database
image : postgres
restart : always
volumes :
- database-data:/var/lib/postgresql
environment :
- POSTGRES_USER=unicorn_user
- POSTGRES_PASSWORD=magical_password
- POSTGRES_DB=calendso
networks :
- stack
volumes :
database-data :
Change default credentials in production: environment :
- POSTGRES_USER=calcom_prod
- POSTGRES_PASSWORD=${DB_PASSWORD} # Use env var
- POSTGRES_DB=calendso
Separate Databases
Insights Database
For analytics workload isolation:
INSIGHTS_DATABASE_URL = "postgresql://user:pass@analytics-db:5432/insights"
SAML Database (Enterprise)
For SAML SSO data:
SAML_DATABASE_URL = "postgresql://user:pass@host:5432/calendso_saml"
Create the SAML database:
CREATE DATABASE calendso_saml ;
GRANT ALL PRIVILEGES ON DATABASE calendso_saml TO calcom_user;
Troubleshooting
Connection Refused
Error: connect ECONNREFUSED 127.0.0.1:5432
Solutions:
Verify PostgreSQL is running: sudo systemctl status postgresql
Check port: sudo netstat -plnt | grep 5432
Review pg_hba.conf for connection permissions
Too Many Connections
Error: sorry, too many clients already
Solutions:
Implement connection pooling (PgBouncer)
Increase max_connections in PostgreSQL
Check for connection leaks in application
Migration Failures
Error: Migration failed to apply
Solutions:
Use DATABASE_DIRECT_URL (not pooled connection)
Check database permissions
Review migration logs: yarn workspace @calcom/prisma db-migrate status
Manually resolve conflicts in failed migrations
Permission Denied
Error: permission denied for schema public
Solution (PostgreSQL 15+):
\c calendso
GRANT ALL ON SCHEMA public TO calcom_user;
GRANT ALL ON ALL TABLES IN SCHEMA public TO calcom_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO calcom_user;
Authentication Failed
Error: password authentication failed for user "calcom_user"
Solutions:
Verify credentials in DATABASE_URL
URL-encode special characters in password
Check pg_hba.conf authentication method (md5/scram-sha-256)
Query Statistics
Enable pg_stat_statements extension:
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- View slow queries
SELECT query, calls, total_time, mean_time, max_time
FROM pg_stat_statements
ORDER BY total_time DESC
LIMIT 20 ;
Connection Monitoring
-- Current connections
SELECT count ( * ) FROM pg_stat_activity;
-- Connections by state
SELECT state , count ( * )
FROM pg_stat_activity
GROUP BY state ;
-- Long-running queries
SELECT pid, now () - query_start AS duration, query
FROM pg_stat_activity
WHERE state = 'active'
ORDER BY duration DESC ;
Production Checklist
Next Steps
Review Configuration for database-related environment variables
See Docker Setup for containerized database deployment
Check Deployment for platform-specific database configurations