Cal.diy Docker Compose 部署指南

用 Docker Compose 部署 Cal.diy v6.2.0:cal.com/cal.com 镜像位甄别(cal.diy 无 tag 坑)、.env 密钥生成、4 服务编排(Web/PostgreSQL/Redis/API v2)、588 个迁移首次启动回显、wait-for-it 与 STRIPE_API_KEY 崩溃循环排查。Docker Engine 29.8.1 逐命令实测。

Cal.diyDocker ComposePostgreSQL安装部署2026/10/19

Cal.diy Docker Compose 部署指南

目标:用 Docker Compose 在单机部署 Cal.diy v6.2.0 自托管预约调度系统,包含 Web 应用、PostgreSQL 数据库、Redis 与 API v2 四个服务,逐命令实测(真实回显),覆盖首次启动迁移、管理员初始化与常见启动失败排查。已在 Docker Engine 29.8.1(Compose v5.5.1)实测通过。

一、环境信息

  • 实测服务器:Linux 5.15 内核,x86_64
  • Docker Engine 29.8.1,Docker Compose v5.5.1
  • 镜像版本:calcom/cal.com:v6.2.0(Cal.diy 的官方应用发布位,2026-03-02 推送)、postgres:16、redis:7
  • 组件版本核实:GitHub Releases 最新稳定版 v6.2.0(2026-03-01 发布,非 Pre-release),查询日期 2026-10-19
  • 网络:专用 compose 网络 cal-diy-stack,显式子网 10.89.56.0/24
  • 宿主端口:8127(Web 应用)、8128(PostgreSQL 维护口)、8129(API v2)
  • 资源占用:四个服务合计内存上限 3.6G(db 512m + redis 128m + web 1536m + api 1536m),实测稳态 RSS 约 1.6G

部署前核对宿主端口未被占用:

# ss -ltn | grep -E ':(8127|8128|8129)\s'
(无输出,端口空闲)

创建部署目录:

# mkdir -p /opt/data/cal.diy-v6.2.0 && cd /opt/data/cal.diy-v6.2.0

注:本文实测工作目录为 /opt/data/quzuo/cal.diy-v6.2.0,读者可按自身磁盘规划调整。

二、镜像选型

Cal.diy 的镜像发布位有两个易混淆的名称,2026-10-19 在 Docker Hub 实查结论:

仓库结论
calcom/cal.com官方发布位。5,155,588 pulls,tags 含 latest、v6.2.0、v6.2.0-arm(2026-03-02 更新)。选它。
calcom/cal.diy仓库存在但 0 个 tag,docker pull calcom/cal.diy 会报 manifest unknown。官方文档指向它与实际发布位不一致,勿用。

关于 API v2:官方 Compose 文件中 calcom-api 服务只有 build: 定义(从源码构建),没有对应的预构建镜像发布位。如需 API v2,须从 v6.2.0 源码 tag 自行构建(本章第五节给出实测构建流程);仅用 Web 应用与预约页的话可以完全不部署它。

版本锁定建议:使用 v6.2.0 明确 tag 而非 latest,与《Cal.diy 介绍与选型指南》核实的最新稳定版对应。

三、前置条件

  • Docker Engine 与 Compose 插件已安装,且当前用户在 docker 组(或以 root 执行)
  • 服务器时间已通过 NTP 校准(预约系统对时间敏感,站内《时间同步与服务》有部署方法)
  • 内存 ≥ 2G 空闲
  • 如需从 Docker Hub 拉取镜像缓慢,可配置镜像加速或代理:
# export https_proxy=http://192.168.1.62:7897 http_proxy=http://192.168.1.62:7897

四、部署步骤

4.1 编写 .env

创建 /opt/data/cal.diy-v6.2.0/.env(密钥值须替换为自己的随机串,生成命令见 4.2):

POSTGRES_USER=cal_com
POSTGRES_PASSWORD=q1w2e3r4
POSTGRES_DB=cal_com
DATABASE_HOST=database
NEXT_PUBLIC_WEBAPP_URL=http://192.168.146.128:8127
NEXT_PUBLIC_WEBSITE_URL=http://192.168.146.128:8127
NEXTAUTH_URL=http://192.168.146.128:8127
NEXTAUTH_SECRET=EdA+dt9c3AJXpN83ojjsKF1cS+PNSFiPWxHzQUQK3qo=
CALENDSO_ENCRYPTION_KEY=VvTwwmXeLHe4IYqzZ90gzNIc7tbaDxDn
ALLOWED_HOSTNAMES='"192.168.146.128:8127","localhost:3000"'
CALCOM_TELEMETRY_DISABLED=1
JWT_SECRET=(随机生成,API v2 用)
STRIPE_API_KEY=
STRIPE_WEBHOOK_SECRET=

要点:

  • NEXT_PUBLIC_WEBAPP_URL / NEXTAUTH_URL 必须写成访问者实际使用的地址(含端口),否则预约页跳转与登录回调会落到错误域名
  • ALLOWED_HOSTNAMES 是 JSON 数组格式的字符串(内层双引号不能省),非标准域名/IP 访问必须配置,否则 403
  • STRIPE_API_KEY 值留空即可,但变量必须存在(空值可过启动校验,缺失变量会崩溃循环,见 4.6 与第八章)
  • 邮箱登录(magic link)依赖 SMTP 配置,本文采用账号密码注册,不需要 SMTP 变量

4.2 生成随机密钥

# openssl rand -base64 32
EdA+dt9c3AJXpN83ojjsKF1cS+PNSFiPWxHzQUQK3qo=
# openssl rand -base64 24
VvTwwmXeLHe4IYqzZ90gzNIc7tbaDxDn

NEXTAUTH_SECRET 与 JWT_SECRET 各生成一个;CALENDSO_ENCRYPTION_KEY 官方文档要求 24 位,可用 openssl rand -base64 24。这两个密钥生成后必须妥善保存,更换会导致全部会话失效、第三方凭据无法解密。

4.3 编写 docker-compose.yml

name: cal-diy

volumes:
  database-data:
  redis-data:

networks:
  stack:
    name: cal-diy-stack
    ipam:
      config:
        - subnet: 10.89.56.0/24

services:
  database:
    container_name: cal-com-6-2-0-db
    image: postgres:16
    restart: always
    volumes:
      - database-data:/var/lib/postgresql
    environment:
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=${POSTGRES_DB}
    ports:
      - "8128:5432"
    networks:
      - stack
    mem_limit: 512m
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 6

  redis:
    container_name: cal-com-6-2-0-redis
    image: redis:7
    restart: always
    volumes:
      - redis-data:/data
    networks:
      - stack
    mem_limit: 128m

  calcom:
    container_name: cal-com-6-2-0-web
    image: calcom/cal.com:v6.2.0
    restart: always
    networks:
      - stack
    ports:
      - "8127:3000"
    env_file: .env
    environment:
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}:5432/${POSTGRES_DB}
      - DATABASE_DIRECT_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}:5432/${POSTGRES_DB}
    depends_on:
      - database
    mem_limit: 1536m

与官方 compose 的差异说明(官方 138 行原文件见 https://cal.diy/docker):

  • 官方 database 卷挂载是 database-data:/var/lib/postgresql(含 data 子目录自动继承,勿改为 /var/lib/postgresql/data,二者卷升级语义不同,保持官方写法)
  • 本文增加了显式子网、内存上限、db 维护端口 8128 与 healthcheck,均为运维增强项,不影响官方行为
  • PostgreSQL 数据卷在应用外独立成卷,升级 Web 应用版本时数据库结构由启动时的 prisma migrate deploy 自动前滚,升级前仍需备份(见《Cal.diy 日常运维与排障手册》)

4.4 启动并核对网络

# docker compose up -d
[+] Running 21/21
 ✔ calcom Pulled                                                                    142.7s
   ✔ e9a3d6bcbcbf Pull complete
   ...(共 21 层,略)
 ✔ 4 Networks Created
 ✔ Container cal-com-6-2-0-db      Started
 ✔ Container cal-com-6-2-0-redis   Started
 ✔ Container cal-com-6-2-0-web     Started

确认网络子网与出网路径:

# docker network inspect cal-diy-stack --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
10.89.56.0/24
# ip route get 192.168.1.62
192.168.1.62 via 192.168.146.2 dev ens33 src 192.168.146.128

首次启动会拉取镜像并执行数据库迁移,整个 Web 服务就绪约需 1-2 分钟。观察迁移日志:

# docker logs cal-com-6-2-0-web 2>&1 | grep -E "migrat|Applying" | tail -6
Applying migration `20250904135225_add_oauth_access_token_expiry`
Applying migration `20251007125405_add_booking_phone_number`
Applying migration `20251103000000_fix_booking_status_denormalization`
Applying migration `20251201000000_rename_booking_uid_index`
Applying migration `20260205133541_profile_username_migration`
Applying migration `20260218084325_team_user_field_index`
588 migrations found in prisma/migrations
Following migration had more than one parent: 20250902132329_test_single_booking_mode_reminder_emails
...
All migrations have been successfully applied.

(v6.2.0 全量迁移 588 个,空库首次启动约 1-3 分钟执行完毕,以 All migrations have been successfully applied. 为准。)

4.5 验证服务状态

# docker compose ps
NAME                  IMAGE                      STATUS
cal-com-6-2-0-web     calcom/cal.com:v6.2.0      Up 3 minutes (healthy)
cal-com-6-2-0-db      postgres:16                Up 3 minutes (healthy)
cal-com-6-2-0-redis   redis:7                    Up 3 minutes
# curl -s -o /dev/null -w "%{http_code}\n" http://192.168.146.128:8127/
200
# curl -s http://192.168.146.128:8127/ -L -o /dev/null -w "%{url_effective} -> %{http_code}\n"
http://192.168.146.128:8127/auth/setup?step=1 -> 200

首次访问根路径会 302 到 /auth/setup?step=1 初始化向导,按页面提示设置姓名、用户名、邮箱与密码即完成第一个账号创建。密码策略实测要求:至少 7 位且同时包含大写字母、小写字母与数字(源码 packages/lib/auth/isPasswordValid.ts),例如 Q1w2e3r4。

首个注册用户默认是普通角色,访问管理后台前需在数据库提权(实测复现):

# docker exec cal-com-6-2-0-db psql -U cal_com -d cal_com \
    -c "UPDATE users SET role='ADMIN' WHERE id=1 RETURNING id, username, role;"
 id | username | role
----+----------+-------
  1 | admin    | ADMIN
(1 row)

UPDATE 1

提权后重新登录,访问 /settings/admin/flags 可见 Feature flags 管理页即生效。

4.6 常见启动失败与修复

现象 1:Web 容器日志大量 Unknown argument: database

# docker logs cal-com-6-2-0-web 2>&1 | head -20
Usage: /calcom/scripts/wait-for-it.sh host:port [-s] [-t timeout] [-- command args]
       -q: silent mode...
Unknown argument: database

v6.2.0 的启动脚本以裸主机名调用 wait-for-it.sh,而该脚本要求 host:port 形式,属上游脚本缺陷。实测影响有限:脚本报错后继续执行迁移,此时 PostgreSQL 通常已就绪,服务正常拉起。若迁移偶发连接失败,重启一次即可(docker compose restart calcom)。

现象 2:API v2 容器崩溃循环 Missing environment variable: STRIPE_API_KEY.

# docker logs cal-com-6-2-0-api 2>&1 | grep -m1 "ExceptionHandler"
{"message":"Missing environment variable: STRIPE_API_KEY.","level":"error",...}

API v2 启动时强制要求 STRIPE_API_KEY、STRIPE_WEBHOOK_SECRET 变量存在(自托管无 Stripe 也要求占位)。在 .env 中补上两个空值变量并 docker compose up -d calcom-api 即可恢复。这是 Cal.diy 社区版最典型的启动坑。

现象 3:访问 403

ALLOWED_HOSTNAMES 未包含访问所用的主机名:端口组合,按 4.1 补齐后重建 Web 容器(docker compose up -d --force-recreate calcom)。

五、部署 API v2(可选)

API v2 是独立 NestJS 服务,官方无预构建镜像,需从源码构建。v6.2.0 源码获取:

# git clone --depth 1 --branch v6.2.0 https://github.com/calcom/cal.diy src
(约 550MB)

构建镜像(实测 168 秒完成,产物 2.38GB 压缩体积):

# docker build -t cal-com-6-2-0-api:v6.2.0 src/apps/api/v2
#14 DONE 168.2s
# docker images cal-com-6-2-0-api
REPOSITORY             TAG      IMAGE ID       CREATED         SIZE
cal-com-6-2-0-api      v6.2.0   c2d59eb7847c   5 minutes ago   2.38GB

在 docker-compose.yml 追加服务(环境变量与 Web 应用共用 .env):

  calcom-api:
    container_name: cal-com-6-2-0-api
    image: cal-com-6-2-0-api:v6.2.0
    restart: always
    networks:
      - stack
    ports:
      - "8129:80"
    environment:
      - API_PORT=80
      - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}:5432/${POSTGRES_DB}
      - DATABASE_DIRECT_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}:5432/${POSTGRES_DB}
      - DATABASE_READ_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}:5432/${POSTGRES_DB}
      - DATABASE_WRITE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DATABASE_HOST}:5432/${POSTGRES_DB}
      - REDIS_URL=redis://redis:6379
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - JWT_SECRET=${JWT_SECRET}
      - CALENDSO_ENCRYPTION_KEY=${CALENDSO_ENCRYPTION_KEY}
      - WEB_APP_URL=${NEXT_PUBLIC_WEBAPP_URL}
      - STRIPE_API_KEY=${STRIPE_API_KEY}
      - STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
      - CALCOM_TELEMETRY_DISABLED=1
    depends_on:
      - database
      - redis
    mem_limit: 1536m

启动并验证:

# docker compose up -d calcom-api
# docker logs cal-com-6-2-0-api 2>&1 | tail -3
[PrismaReadService] Connected to read database
[PrismaWriteService] Connected to write database
[NestJS worker application bootstrapped successfully.
[RedisService] IoRedis connected!
# curl -s http://192.168.146.128:8129/v2/me | head -c 200
{"status":"error","timestamp":"...","path":"/v2/me","error":{"code":"UnauthorizedException","message":"ApiAuthStrategy - No authentication method provided. ...

返回业务级 401(而非 502/404)即说明 API v2 已就绪。注意社区版 API v2 的 license 限制:API Key 认证链路校验 CALCOM_LICENSE_KEY(企业授权),未配置时任何 API Key 都会得到 401 Invalid or missing CALCOM_LICENSE_KEY;携带 Web 会话 Cookie(NEXT_AUTH 通道)调用不经此校验,实测可用,复现流程见《Cal.diy 快速入门与实战示例》。

六、数据持久化

数据载体说明
预约/用户/事件类型等业务数据卷 cal-diy_database-data → /var/lib/postgresql核心状态,备份重点是它
Redis 缓存与队列卷 cal-diy_redis-data → /data可丢失性数据,重建容器不丢业务数据
应用静态资源随镜像无状态,版本由镜像 tag 固定

验证卷已生成:

# docker volume ls | grep cal-diy
local     cal-diy_database-data
local     cal-diy_redis-data

宿主侧直连数据库验证(8128 端口):

# docker exec cal-com-6-2-0-db psql -U cal_com -d cal_com -c '\dt' | head -5
             List of relations
 Schema |           Name           | Type  |  Owner
--------+--------------------------+-------+---------
 public | Account                  | table | cal_com
 public | ApiKey                   | table | cal_com
 public | App                      | table | cal_com

表名混合大小写(Prisma 默认 PascalCase),SQL 维护时必须带双引号,详见《Cal.diy 日常运维与排障手册》。

七、环境变量速查

变量必填说明
DATABASE_URL / DATABASE_DIRECT_URL是PostgreSQL 连接串,compose 内指向 database 服务
NEXTAUTH_URL是站点对外地址(含协议与端口),登录回调基准
NEXT_PUBLIC_WEBAPP_URL / NEXT_PUBLIC_WEBSITE_URL是应用与站点对外地址,预约页链接生成基准
NEXTAUTH_SECRET是会话签名密钥,openssl rand -base64 32 生成
CALENDSO_ENCRYPTION_KEY是第三方凭据加密密钥(24 位),更换后已存凭据不可解密
ALLOWED_HOSTNAMES建议配允许访问的主机名:端口 JSON 数组,非标域名访问必配
CALCOM_TELEMETRY_DISABLED建议设 1 关闭匿名遥测
STRIPE_API_KEY / STRIPE_WEBHOOK_SECRETAPI v2 必填变量必须存在,自托管可留空
JWT_SECRETAPI v2 必填API v2 令牌签名密钥
CALCOM_LICENSE_KEY否企业 license,社区版不配置则 API Key 通道不可用
EMAIL_SERVER_HOST / EMAIL_SERVER_PORT否SMTP 服务器,配置后启用 magic link 登录与邮件通知

八、常见问题

Q1:首次打开一直跳 setup 向导,重复填写? NEXTAUTH_URL 与浏览器访问地址不一致时 Cookie 域不匹配,向导状态无法保存。核对 .env 三处 URL 变量与实际访问地址完全一致后 --force-recreate 重建 Web 容器。

Q2:注册时提示密码不合规? 策略为 ≥7 位且含大写+小写+数字。q1w2e3r4 缺大写会被拒,改用 Q1w2e3r4 类口令。

Q3:API v2 一直重启? 看日志是否 Missing environment variable: STRIPE_API_KEY.,按 4.6 现象 2 处理。

Q4:预约页能打开但登录后跳回首页? 多为 ALLOWED_HOSTNAMES 缺项或 Cookie Secure 语义与 HTTP 站点冲突:自托管 HTTP 站点确保未设置 NEXTAUTH_COOKIE_SECURE=true 之类的 HTTPS 专用变量。

Q5:如何完全重置环境? docker compose down -v 删除卷即清空全部数据(会删库,先备份);仅重建应用不动数据用 docker compose restart calcom。

九、参考链接