{"id":31017,"date":"2026-05-18T10:16:03","date_gmt":"2026-05-18T08:16:03","guid":{"rendered":"https:\/\/contabo.com\/blog\/?p=31017"},"modified":"2026-06-01T13:44:16","modified_gmt":"2026-06-01T11:44:16","slug":"self-host-cal-com-with-docker-and-postgresql","status":"publish","type":"post","link":"https:\/\/contabo.com\/blog\/self-host-cal-com-with-docker-and-postgresql\/","title":{"rendered":"How to Self-Host cal.com with Docker and PostgreSQL"},"content":{"rendered":"\n<p>Calendly charges per seat, per month, forever. cal.com is the open-source alternative that runs on your own server with no per-user pricing \u2014 booking pages, team scheduling, round-robin assignment, calendar sync, and webhook automation, all yours for the cost of a VPS.<\/p>\n\n\n\n<p>This guide covers self-hosting cal.com with <a href=\"https:\/\/contabo.com\/blog\/how-to-host-docker\/\">Docker <\/a>and <a href=\"https:\/\/contabo.com\/blog\/open-source-database-series-postgresql\/\">PostgreSQL<\/a>. The .env variable names are verified against the current cal.com GitHub repository as of May 2026, but cal.com adds new variables regularly \u2014 always cross-reference the current .env.example in the repo before your first run. Cal.diy, the community fork, follows the same installation process.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-cal-com-does-and-who-should-self-host-it\">What cal.com Does and Who Should Self-Host It<\/h2>\n\n\n\n<p>cal.com is an open-source scheduling platform \u2014 the infrastructure behind booking pages, availability rules, round-robin routing, team collective scheduling, and calendar sync with Google, Outlook, and iCal. The feature set is comparable to Calendly. The pricing difference is stark: Calendly&#8217;s Teams plan runs $16\u201320 per user per month. Self-hosted cal.com costs whatever your server costs.<\/p>\n\n\n\n<p>Three situations where self-hosting makes obvious sense: GDPR or data residency requirements mean you cannot send booking data through a third-party SaaS. You have hit Calendly&#8217;s per-seat ceiling at team scale. You want <a href=\"https:\/\/contabo.com\/en\/contabo-api\/\">API<\/a>-level control for custom integrations.<\/p>\n\n\n\n<p>If you just want a booking link up in five minutes with zero server to manage, cal.com&#8217;s hosted free tier is the simpler path. This guide is for teams that have already decided self-hosting is the right call.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-server-and-software-requirements\">Server and Software Requirements<\/h2>\n\n\n\n<p>cal.com is a Next.js application backed by PostgreSQL. The footprint is reasonable.<\/p>\n\n\n\n<p>Minimum: 2 vCPU, 4 GB RAM, 20 GB disk. This covers individual use and small teams with a single cal.com instance and its Postgres database.<\/p>\n\n\n\n<p>Recommended for co-located services: 4 vCPU, 8 GB RAM. If you plan to run cal.com alongside n8n or other tools on the same server, 8 GB gives each service enough headroom.<\/p>\n\n\n\n<p>Software: <a href=\"https:\/\/contabo.com\/blog\/docker-explained\/\">Docker <\/a>and Docker Compose (the plugin version \u2014 &#8216;docker compose&#8217; without the hyphen), a domain with DNS access, and <a href=\"https:\/\/contabo.com\/blog\/how-to-set-up-a-smtp-server\/\">SMTP <\/a>credentials for booking email notifications. Ubuntu 22.04 or 24.04 is the recommended OS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-1-clone-the-repo-and-configure-your-environment\">Step 1 \u2014 Clone the Repo and Configure Your Environment<\/h2>\n\n\n\n<p>The Docker configuration is maintained in the main cal.com monorepo \u2014 clone it and copy the environment template:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>git clone https:\/\/github.com\/calcom\/cal.com.git cd cal.com cp .env.example .env<\/code><\/pre>\n\n\n\n<p>Open <code>.env<\/code> and set the following variables. These are the variables verified as required in May 2026 \u2014 always check the current <code>.env.example<\/code> for any additions:<\/p>\n\n\n\n<p>DATABASE_URL \u2014 your PostgreSQL connection string. Format: <code>postgresql:\/\/USER:PASSWORD@HOST:5432\/DBNAME<\/code>. The bundled Postgres container default is <code>postgresql:\/\/unicorn_user:magical_password@database:5432\/calendso<\/code> \u2014 change those credentials before running.<\/p>\n\n\n\n<p>NEXTAUTH_SECRET \u2014 session encryption key. Generate with: <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>openssl rand -base64 32<\/code><\/code><\/pre>\n\n\n\n<p>CALENDSO_ENCRYPTION_KEY \u2014 credential encryption key. Generate with: <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>openssl rand -base64 24 <\/code><\/code><\/pre>\n\n\n\n<p>This key uses 24 bytes (not 32). It must never be changed after your first run \u2014 changing it permanently corrupts all stored integration credentials (Google OAuth tokens, calendar connections, etc.). Back it up externally immediately.<\/p>\n\n\n\n<p>NEXT_PUBLIC_WEBAPP_URL and NEXTAUTH_URL \u2014 both set to the full public URL where cal.com will be accessible: https:\/\/cal.yourdomain.com<\/p>\n\n\n\n<p>EMAIL_FROM, EMAIL_SERVER_HOST, EMAIL_SERVER_PORT, EMAIL_SERVER_USER, EMAIL_SERVER_PASSWORD \u2014 SMTP provider credentials for booking confirmation emails. Resend and Postmark are reliable options.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-2-start-the-stack\">Step 2 \u2014 Start the Stack<\/h2>\n\n\n\n<p>With <code>.env<\/code> configured, start the full stack \u2014 cal.com, PostgreSQL, and Prisma Studio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose up -d<\/code><\/pre>\n\n\n\n<p>The first run pulls images and runs database migrations automatically. It takes a few minutes. Access cal.com at http:\/\/YOUR_SERVER_IP:3000 (or your defined NEXT_PUBLIC_WEBAPP_URL). A setup wizard runs on first load \u2014 create your admin account and set your organisation name.<\/p>\n\n\n\n<p>Prisma Studio (database browser) runs on port 5555 if you need to inspect the database directly.<\/p>\n\n\n\n<p>Test before touching DNS: create a test event type, make a booking, and confirm the booking appears in your admin view. If email notifications aren&#8217;t arriving, check your SMTP variables \u2014 that&#8217;s the most common first-run issue.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-3-domain-and-https\">Step 3 \u2014 Domain and HTTPS<\/h2>\n\n\n\n<p>Create an A record in DNS pointing your domain to your server&#8217;s IP. Then configure a reverse proxy in front of cal.com&#8217;s port 3000 with SSL. <a href=\"https:\/\/contabo.com\/blog\/nginx-configuration-beginners-guide\/\">Nginx <\/a>with Certbot and Caddy are both common choices:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Proxy all traffic on port 443 (HTTPS) to localhost:3000<\/li>\n\n\n\n<li>Provision a Let&#8217;s Encrypt certificate for your domain<\/li>\n\n\n\n<li>Redirect HTTP (port 80) to HTTPS<\/li>\n<\/ul>\n\n\n\n<p>After the proxy is in place, update NEXT_PUBLIC_WEBAPP_URL and NEXTAUTH_URL in <code>.env<\/code> to the https:\/\/ version of your domain, then restart:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose restart calcom<\/code><\/pre>\n\n\n\n<p>Test end-to-end: open the HTTPS URL, log in, create a booking, and confirm the confirmation email arrives with the correct domain in the link. If email links still show localhost or http:\/\/, the <code>env vars<\/code> haven&#8217;t taken effect \u2014 check for typos and restart.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-step-4-keeping-cal-com-updated\">Step 4 \u2014 Keeping cal.com Updated<\/h2>\n\n\n\n<p>Before any update, back up your database. The exact<code> pg_dump<\/code> command depends on the container name in your Compose file \u2014 check with docker compose ps to confirm your Postgres container name, then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker exec -t &lt;postgres-container-name&gt; pg_dump -U unicorn_user calendso &gt; backup_$(date +%Y%m%d).sql<\/code><\/pre>\n\n\n\n<p>Then pull new images and restart:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose pull docker compose up -d<\/code><\/pre>\n\n\n\n<p>cal.com runs database migrations automatically on startup when schema changes are present. Check container logs after an update to confirm migrations completed before sending traffic.<\/p>\n\n\n\n<p>Never regenerate CALENDSO_ENCRYPTION_KEY during an update. It is not a password you rotate \u2014 it is a master encryption key. Changing it corrupts all stored credentials.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-connecting-google-calendar-and-email\">Connecting Google Calendar and Email<\/h2>\n\n\n\n<p>For Google Calendar sync, create an OAuth 2.0 application in the Google Cloud Console with the Google Calendar API enabled. Add the client credentials to <code>.env<\/code> under the Google-related variables \u2014 check the current <code>.env.example<\/code> for the exact variable names, as they have been renamed between cal.com releases.<\/p>\n\n\n\n<p>Set the authorised redirect URI to https:\/\/cal.yourdomain.com\/api\/auth\/callback\/google.<\/p>\n\n\n\n<p>In the cal.com admin panel, app integrations are enabled under Settings \u2192 Apps. Users can then connect their individual Google accounts from their own settings pages.<\/p>\n\n\n\n<p>SMTP for booking emails should already be working if you set the EMAIL_SERVER_* variables in Step 1. If confirmation emails aren&#8217;t arriving: verify port 587 is not blocked by your <a href=\"https:\/\/contabo.com\/en\/vps\/\">VPS <\/a>provider, check you&#8217;re using a transactional (not marketing) email API key, and check docker logs calcom for the SMTP error.<\/p>\n\n\n\n<p><strong><em>A note on hosting<\/em><\/strong><\/p>\n\n\n\n<p>cal.com is a Next.js app with a PostgreSQL database \u2014 lean by self-hosted standards. A solo user or small team runs comfortably on 8 GB RAM. The upgrade case is when you start co-locating cal.com with other tools: n8n for booking automation, analytics, or additional services on the same box. For that, 12\u201324 GB gives each service room without resource contention. <a href=\"https:\/\/contabo.com\/en\/vps\/\">Contabo Cloud VPS 10<\/a> (8 GB RAM, \u20ac4,28\/mo) is a practical starting point; Cloud VPS 30 (13 GB RAM, \u20ac6,33\/mo) makes sense if cal.com is part of a wider self-hosted stack on the same server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-faq-self-hosting-cal-com\">FAQ: Self-Hosting cal.com<\/h2>\n\n\n\n<div class=\"schema-faq wp-block-yoast-faq-block\"><div class=\"schema-faq-section\" id=\"faq-question-1780299694109\"><strong class=\"schema-faq-question\">Do I need to run database migrations when updating cal.com?<\/strong> <p class=\"schema-faq-answer\">In most cases, yes \u2014 cal.com runs Prisma migrations automatically on container startup when schema changes are present. After pulling new images and running docker compose up -d, check the application container logs to confirm migrations completed successfully before sending traffic:<br><code>docker compose logs calcom --tail=50<\/code><br>Look for a line indicating migrations applied or &#8216;No pending migrations.&#8217; If the container fails to start after an update, a failed migration is usually the cause \u2014 check the logs, verify DATABASE_URL is correct, and confirm the Postgres container is healthy before retrying.<br>Always take a database backup before any update. A broken migration against a live database without a backup is a bad day.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1780299828194\"><strong class=\"schema-faq-question\">What are the server requirements for self-hosting cal.com?<\/strong> <p class=\"schema-faq-answer\">Minimum: 2 vCPU, 4 GB RAM, 20 GB disk, Ubuntu 22.04 or 24.04 with Docker. 4 vCPU and 8 GB RAM is more comfortable for a team deployment, especially if other services share the same server.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1780300356721\"><strong class=\"schema-faq-question\">How do I connect cal.com to PostgreSQL?<\/strong> <p class=\"schema-faq-answer\">Set DATABASE_URL in .env to a PostgreSQL connection string: postgresql:\/\/USER:PASSWORD@HOST:5432\/DBNAME. The bundled docker compose file includes a Postgres container \u2014 use that connection string for a single-server setup. cal.com runs migrations automatically on first startup.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1780300366184\"><strong class=\"schema-faq-question\">Can I use cal.com with my own domain?<\/strong> <p class=\"schema-faq-answer\">Yes. Set NEXT_PUBLIC_WEBAPP_URL and NEXTAUTH_URL in <code>.env<\/code> to your full domain (https:\/\/cal.yourdomain.com), configure a reverse proxy pointing to port 3000 with SSL, and booking pages will serve from your own domain.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1780300382397\"><strong class=\"schema-faq-question\">What happens if I change CALENDSO_ENCRYPTION_KEY after setup?<\/strong> <p class=\"schema-faq-answer\">Every stored credential in your cal.com instance \u2014 Google OAuth tokens, calendar connections, integration secrets \u2014 is encrypted using this key. Changing it after your first run does not re-encrypt the stored data; it just makes it permanently unreadable.<br>The result: all calendar integrations break silently. Users lose their Google Calendar connections. The app may start, but integrations stop working with no clear error message.<br>Treat CALENDSO_ENCRYPTION_KEY like a database master key \u2014 generate it once with openssl rand -base64 24, store it somewhere safe outside the server, and never change it. If you lose it and need to rotate it, cal.com&#8217;s documentation covers the migration procedure for that specific scenario.<\/p> <\/div> <div class=\"schema-faq-section\" id=\"faq-question-1780300401732\"><strong class=\"schema-faq-question\">How do I set up email for self-hosted cal.com?<\/strong> <p class=\"schema-faq-answer\">Set EMAIL_FROM, EMAIL_SERVER_HOST, EMAIL_SERVER_PORT, EMAIL_SERVER_USER, and EMAIL_SERVER_PASSWORD in <code>.env<\/code> with your SMTP provider credentials. Resend and Postmark are reliable options for transactional email. Restart after<code> .env <\/code>changes: docker compose restart calcom.<\/p> <\/div> <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Self-hosting cal.com gives you full control over scheduling data, integrations, and costs. This guide explains how to deploy cal.com with Docker and PostgreSQL, configure HTTPS and email, and avoid common setup mistakes during updates and first-run configuration.<\/p>\n","protected":false},"author":44,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"_uag_custom_page_level_css":"","site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[18],"tags":[4436,1471,4271,3744],"ppma_author":[3402],"class_list":["post-31017","post","type-post","status-publish","format-standard","hentry","category-tutorials","tag-cal-com","tag-docker","tag-http","tag-postgresql"],"uagb_featured_image_src":{"full":false,"thumbnail":false,"medium":false,"medium_large":false,"large":false,"1536x1536":false,"2048x2048":false},"uagb_author_info":{"display_name":"Milan Ivanovic","author_link":"https:\/\/contabo.com\/blog\/author\/milan\/"},"uagb_comment_info":0,"uagb_excerpt":"Self-hosting cal.com gives you full control over scheduling data, integrations, and costs. This guide explains how to deploy cal.com with Docker and PostgreSQL, configure HTTPS and email, and avoid common setup mistakes during updates and first-run configuration.","authors":[{"term_id":3402,"user_id":0,"is_guest":1,"slug":"contabro","display_name":"ContaBro","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/posts\/31017","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/users\/44"}],"replies":[{"embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/comments?post=31017"}],"version-history":[{"count":8,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/posts\/31017\/revisions"}],"predecessor-version":[{"id":31065,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/posts\/31017\/revisions\/31065"}],"wp:attachment":[{"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/media?parent=31017"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/categories?post=31017"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/tags?post=31017"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/contabo.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=31017"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}