Dittofeed v0.24.0-alpha.21 Docker Compose 部署指南

在 Ubuntu 上以官方 lite Compose 栈部署 Dittofeed v0.24.0-alpha.21:Postgres 15 + ClickHouse 24.12 + Temporal 1.22 依赖顺序、健康检查、Nginx 反代、控制台登录与创建 Journey 实测、升级回滚与排障实录。

DittofeedNode.jsTypeScript自动化部署营销自动化2026/10/27

Dittofeed v0.24.0-alpha.21 Docker Compose 部署指南

目标:在一台 Linux 服务器上用官方 lite Compose 栈部署 Dittofeed v0.24.0-alpha.21,并完成「控制台登录 → 创建 Journey → API 回读」的闭环验证。文中命令与输出均为本机实跑回显(2026-10-27),已脱敏口令与密钥。

一、环境信息

项目实测值
操作系统Ubuntu 26.04.1 LTS,内核 7.0.0-38-generic(步骤同样适用于 Ubuntu 22.04)
Docker29.9.2(build a82e528)
Docker Composev5.6.1(Compose V2 插件)
主机资源6 vCPU / 18 GiB 内存 / 195 GiB 可用磁盘
部署目录/opt/data/quzuo/tmp/dittofeed
应用版本v0.24.0-alpha.21(GitHub 最新 tag,2026-10-04 发布)
访问地址http://192.168.146.131:8137(宿主机 8137 → 容器 3000)

本轮实测各组件镜像与摘要:

组件镜像镜像大小Digest
应用(lite)dittofeed/dittofeed-lite:v0.24.0-alpha.213.42 GBsha256:2d181902591a185ff43e0a18989f02b7325a302e5802882eb12bd0079d6e2424
业务库postgres:15633 MBsha256:baa22e6286bf832cb147b8bb5aa9d8bc999846d69c21d11bf63f58fbeed1fa48
事件库clickhouse/clickhouse-server:24.12.6.70-alpine718 MBsha256:cd450891db46cc6ffe313ca2b0fb7dbfb897a6873ca74a724cbe050a2cf62621
工作流temporalio/auto-setup:1.22.4520 MBsha256:b8bdd2208877ba6178bd9e46e162590965b170577659f4b1add95d9f1a69a5c2

二、前置条件与资源评估

  1. 端口:应用容器内监听 3000;依赖端口 5432(Postgres)、8123(ClickHouse HTTP)、7233(Temporal gRPC)只在 Compose 内部网络暴露,不映射到宿主机。
  2. 磁盘:四个镜像合计约 5.3 GB,另需为 Postgres、ClickHouse 数据卷预留空间(实测空载数据卷 < 100 MB,按事件量增长)。
  3. 内存:官方 lite 栈给应用进程限 1024 MB、ClickHouse 限 2048 MB。实测空载占用:lite 462 MiB、ClickHouse 400 MiB、Postgres 113 MiB、Temporal 82 MiB,合计约 1.06 GiB。最低 2 vCPU / 4 GiB 可跑通,生产建议 4 vCPU / 8 GiB 起。
  4. 内核参数:ClickHouse 建议 vm.max_map_count >= 262144;本机已满足,未额外调整。
  5. 网络:Compose 需自建 bridge 网络,避免与其他栈网段冲突(本轮使用 10.89.59.0/24)。

三、版本与镜像选型说明

  • 官方 docker-compose.lite.yaml 中 lite 镜像的默认 tag 是 v0.23.0,IMAGE_TAG 默认值同样滞后。必须显式把 tag 覆盖为 v0.24.0-alpha.21,否则部署到的不是本文基线版本。
  • 官方仓库同时提供 docker-compose.yaml(dashboard/api/worker 三进程 + 可选 Kafka + MinIO + MailHog,面向开发)与 docker-compose.lite.yaml(单进程 + Postgres/ClickHouse/Temporal,面向上线)。本文选 lite,理由是部署面最小、控制台与 API 同端口、适合单机。
  • 由于所有 Release 均为 -alpha.N,升级前必须固定 digest 并准备回滚 tag。

四、部署步骤

4.1 拉取源码(用于 Temporal 动态配置挂载)

cd /opt/data/quzuo/tmp/dittofeed
git clone --depth 1 --branch v0.24.0-alpha.21 https://github.com/dittofeed/dittofeed.git repo

回显节选:

Note: switching to 'v0.24.0-alpha.21'.

4.2 准备 .env

cat > .env <<'EOF'
DATABASE_USER=postgres
DATABASE_PASSWORD=Dit2feedPg2026
CLICKHOUSE_USER=dittofeed
CLICKHOUSE_PASSWORD=Dit2feedCh2026
PASSWORD=q1w2e3r4
SECRET_KEY=<openssl rand -base64 32 生成>
DASHBOARD_URL=http://192.168.146.131:8137
DASHBOARD_API_BASE=http://192.168.146.131:8137
EOF

要点:

  • SECRET_KEY 必须是 32 字节随机串的 base64(44 字符),否则 single-tenant 会话无法解密;
  • PASSWORD 是控制台共享登录口令,本文按规范使用 q1w2e3r4;
  • DASHBOARD_URL 用于生成退订链接等外链,DASHBOARD_API_BASE 供前端调用 API,二者都不带 /dashboard、/api 后缀。

4.3 编写 Compose(基于官方 lite 改编)

name: dittofeed-v0.24.0-alpha.21-stack

x-database-credentials: &database-credentials
  DATABASE_USER: ${DATABASE_USER:-postgres}
  DATABASE_PASSWORD: ${DATABASE_PASSWORD:-password}
x-clickhouse-credentials: &clickhouse-credentials
  CLICKHOUSE_USER: ${CLICKHOUSE_USER:-dittofeed}
  CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD:-password}
x-backend-app-env: &backend-app-env
  <<: [*clickhouse-credentials, *database-credentials]
  NODE_ENV: production
  DATABASE_HOST: postgres
  DATABASE_PORT: 5432
  CLICKHOUSE_HOST: http://clickhouse-server:8123
  TEMPORAL_ADDRESS: temporal:7233
  WORKSPACE_NAME: Default
  AUTH_MODE: single-tenant
  SECRET_KEY: ${SECRET_KEY}
  PASSWORD: ${PASSWORD}
  DASHBOARD_URL: ${DASHBOARD_URL}
  DASHBOARD_API_BASE: ${DASHBOARD_API_BASE}

services:
  lite:
    image: dittofeed/dittofeed-lite:v0.24.0-alpha.21
    container_name: dittofeed-v0.24.0-alpha.21-lite
    restart: unless-stopped
    command: node --max-old-space-size=824 ./packages/lite/dist/scripts/startLite.js --workspace-name=Default
    deploy:
      resources:
        limits:
          memory: 1024m
    ports:
      - "8137:3000"
    healthcheck:
      test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3000/').then(r=>process.exit(r.status < 500 ? 0 : 1)).catch(()=>process.exit(1))\""]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 120s
    depends_on:
      postgres:
        condition: service_healthy
      temporal:
        condition: service_started
      clickhouse-server:
        condition: service_healthy
    environment:
      <<: *backend-app-env
      BOOTSTRAP: "true"
      LOG_CONFIG: "true"
    networks:
      - dittofeed-net
  postgres:
    image: postgres:15
    container_name: dittofeed-v0.24.0-alpha.21-postgres
    restart: unless-stopped
    environment:
      POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
      POSTGRES_USER: ${DATABASE_USER}
      POSTGRES_DB: dittofeed
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DATABASE_USER} -d dittofeed"]
      interval: 10s
      timeout: 5s
      retries: 10
    volumes:
      - postgres:/var/lib/postgresql/data
    networks:
      - dittofeed-net
  temporal:
    image: temporalio/auto-setup:1.22.4
    container_name: dittofeed-v0.24.0-alpha.21-temporal
    restart: unless-stopped
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DB: postgresql
      DB_PORT: 5432
      POSTGRES_USER: ${DATABASE_USER}
      POSTGRES_PWD: ${DATABASE_PASSWORD}
      POSTGRES_SEEDS: postgres
      DYNAMIC_CONFIG_FILE_PATH: config/dynamicconfig/prod.yaml
    volumes:
      - ./repo/packages/backend-lib/temporal-dynamicconfig:/etc/temporal/config/dynamicconfig:ro
    networks:
      - dittofeed-net
  clickhouse-server:
    image: clickhouse/clickhouse-server:24.12.6.70-alpine
    container_name: dittofeed-v0.24.0-alpha.21-clickhouse
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 2048m
    environment:
      <<: *clickhouse-credentials
    healthcheck:
      test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8123/ping || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 12
    volumes:
      - clickhouse_lib:/var/lib/clickhouse
      - clickhouse_log:/var/log/clickhouse-server
    networks:
      - dittofeed-net

volumes:
  postgres:
  clickhouse_lib:
  clickhouse_log:

networks:
  dittofeed-net:
    name: dittofeed-v0.24.0-alpha.21-net
    driver: bridge
    ipam:
      config:
        - subnet: 10.89.59.0/24

与官方 lite 的差异:显式项目名与容器名、显式网段、去掉依赖组件的宿主端口映射、为应用与 ClickHouse 增加健康检查。其它环境变量语义与官方一致。

4.4 启动

docker compose --env-file .env config --quiet   # 校验通过则无输出
docker compose up -d

启动回显节选:

 Container dittofeed-v0.24.0-alpha.21-postgres Started
 Container dittofeed-v0.24.0-alpha.21-postgres Healthy
 Container dittofeed-v0.24.0-alpha.21-clickhouse Healthy
 Container dittofeed-v0.24.0-alpha.21-lite Started

应用日志关键行:

{"msg":"Creating ClickHouse database"}
{"msg":"Database already exists"}
{"msg":"Running migrations"}
{"msg":"Server listening at http://0.0.0.0:3000"}
{"msg":"Bootstrapping"}

首次启动会自动建库、跑 Drizzle 迁移并创建 Default 工作区,约 40–60 秒后控制台可用。

五、验证控制台登录与创建 Journey

5.1 容器与健康状态

docker compose ps
NAME                                    IMAGE                                            STATUS                    PORTS
dittofeed-v0.24.0-alpha.21-clickhouse   clickhouse/clickhouse-server:24.12.6.70-alpine   Up (healthy)              8123/tcp, 9000/tcp, 9009/tcp
dittofeed-v0.24.0-alpha.21-lite         dittofeed/dittofeed-lite:v0.24.0-alpha.21        Up (healthy)              0.0.0.0:8137->3000/tcp
dittofeed-v0.24.0-alpha.21-postgres     postgres:15                                      Up (healthy)              5432/tcp
dittofeed-v0.24.0-alpha.21-temporal     temporalio/auto-setup:1.22.4                     Up                        7233/tcp 等

5.2 登录

curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://127.0.0.1:8137/
307 http://127.0.0.1:8137/dashboard

登录页路径是 /dashboard/auth/single-tenant(不是 /dashboard/login):

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8137/dashboard/auth/single-tenant
200

页面会列出安全告警;实测仅提示「Bootstrap is enabled」和「cookie is not secure」,说明自定义 PASSWORD/DATABASE_PASSWORD/CLICKHOUSE_PASSWORD/SECRET_KEY 均已生效(若沿用默认值,会额外出现四条默认口令告警)。

用口令换会话:

curl -s -i -c cookies.txt -H 'Content-Type: application/json' \
  -d '{"password":"q1w2e3r4"}' \
  http://127.0.0.1:8137/api/public/single-tenant/login
HTTP/1.1 200 OK
set-cookie: session=8M%2BrZrM67dAO1ISk6eGyjlj5DdThyHn55XSKmgklyVlhhOmza5Je%3BOmqm5jrN1hOBYNAvMCHDRtEwjlw9Lvb0; Max-Age=1209600; Path=/; HttpOnly

访问受保护页面:

curl -s -b cookies.txt -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8137/dashboard/journeys
200

5.3 创建 Journey(API 实测)

先查工作区 ID:

docker exec dittofeed-v0.24.0-alpha.21-postgres \
  psql -U postgres -d dittofeed -c 'select id,name,status from "Workspace";'
                  id                  |  name   | status
--------------------------------------+---------+--------
 64a328ce-7c53-426a-8733-57207dc474a5 | Default | Active

创建 Admin API Key(用登录会话调用):

curl -s -b cookies.txt -H 'Content-Type: application/json' \
  -d '{"workspaceId":"64a328ce-7c53-426a-8733-57207dc474a5","name":"doc-test-key"}' \
  http://127.0.0.1:8137/api/admin-keys
{"workspaceId":"64a328ce-7c53-426a-8733-57207dc474a5","id":"03b4a00a-a7b0-40ee-9cc8-70517c0486c2","name":"doc-test-key","createdAt":1793104629179,"apiKey":"82445a1f...f750"}

用 API Key 创建「事件触发 → 退出」的最小 Journey:

curl -s -X PUT -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
  --data '{"id":"f7439626-48e5-4e77-a180-6aa5325e7402","workspaceId":"64a328ce-7c53-426a-8733-57207dc474a5","name":"文档验证 Journey","status":"NotStarted","updatedAt":1793104700000,"definition":{"entryNode":{"type":"EventEntryNode","event":"doc.test","child":"ExitNode"},"nodes":[],"exitNode":{"type":"ExitNode"}}}' \
  "http://127.0.0.1:8137/api/admin/journeys/?workspaceId=64a328ce-7c53-426a-8733-57207dc474a5"

回显:

{"id":"f7439626-48e5-4e77-a180-6aa5325e7402","workspaceId":"64a328ce-7c53-426a-8733-57207dc474a5","name":"文档验证 Journey","updatedAt":1793104697568,"status":"NotStarted","createdAt":1793104697568,"definition":{"entryNode":{"type":"EventEntryNode","event":"doc.test","child":"ExitNode"},"exitNode":{"type":"ExitNode"},"nodes":[]}}

回读确认已落库(控制台 Journey 列表走同一份数据):

curl -s -b cookies.txt "http://127.0.0.1:8137/api/journeys?workspaceId=64a328ce-7c53-426a-8733-57207dc474a5"
{"journeys":[{"id":"f7439626-48e5-4e77-a180-6aa5325e7402","name":"文档验证 Journey","status":"NotStarted","canRunMultiple":false,...}]}

至此「登录成功 + Journey 创建成功 + 列表回读成功」闭环完成。

六、反向代理与健康检查

Nginx 反代示例(SESSION_COOKIE_SECURE=true 时建议启用 HTTPS):

server {
    listen 443 ssl;
    server_name dittofeed.example.com;

    ssl_certificate     /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;

    location / {
        proxy_pass         http://127.0.0.1:8137;
        proxy_http_version 1.1;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
        client_max_body_size 20m;
    }
}

反代后必须同步修改 DASHBOARD_URL 与 DASHBOARD_API_BASE 为外部域名,否则邮件里的退订链接、前端 API 请求会指向旧地址。

健康检查有两层:

  • 容器级:Compose 内 lite 的 healthcheck 用 Node 原生 fetch 探测 http://127.0.0.1:3000/,非 5xx 即健康(start_period: 120s 避免启动期误判);ClickHouse 用 /ping,Postgres 用 pg_isready;
  • 外部级:反代或拨测直接请求 /dashboard/auth/single-tenant,期望 200;请求 / 期望 307 到 /dashboard。

七、升级与回滚

升级前:

docker exec dittofeed-v0.24.0-alpha.21-postgres pg_dump -U postgres -d dittofeed -Fc > backup-$(date +%F).dump
docker run --rm -v dittofeed-v0.24.0-alpha.21-stack_clickhouse_lib:/data -v $PWD:/backup alpine \
  tar czf /backup/clickhouse-$(date +%F).tgz -C /data .

升级:

# 1. 修改 compose 中 lite 的 image tag(先改成目标版本)
docker compose pull lite
docker compose up -d lite
# 2. 观察迁移日志,确认无 error
docker logs -f dittofeed-v0.24.0-alpha.21-lite

注意:lite 启动时会自动执行 Postgres 迁移,迁移通常不可逆;跨大版本(如 v0.23 → v0.24)前应先读官方 upgrade-guide。

回滚:

# 把 image 改回上一 tag,重建应用容器(数据卷不动)
docker compose up -d --force-recreate lite

若新版本已执行不兼容迁移,仅回滚镜像可能无法启动,此时需从升级前的 pg_dump 恢复数据库。

八、排障实录

现象原因处理
Bind for :::8129 failed: port is already allocated宿主机 8129 被其他容器占用(ss -ltn 当时未显示,实际由 Docker 端口池占用)换用 8137,并同步改 DASHBOARD_URL/DASHBOARD_API_BASE;部署前用 docker ps --format '{{.Ports}}' 与 ss -ltn 双重确认
访问 /dashboard/login 返回 404版本路由变更,登录页在 /dashboard/auth/single-tenant以 /dashboard/auth/single-tenant 为准;可从 packages/dashboard/src/pages/auth/ 目录确认路由
GET /api/admin/journeys/ 返回 400 querystring must have required property 'workspaceId'Admin API 强制要求工作区 ID从 Workspace 表读取 ID,作为 query 参数传入
Admin API 返回 {"message":"No authorization header"}Admin API 不走控制台会话,必须用 Admin API Key先 POST /api/admin-keys 创建 Key,再带 Authorization: Bearer <key>
PUT /api/admin/journeys/ 返回 Invalid journey id, must be a valid v4 UUIDJourney 主键必须是 v4 UUID,不能用自定义字符串用 uuidgen/Python uuid.uuid4() 生成
控制台页面能开但接口 401SECRET_KEY 变更导致旧 Cookie 失效重新登录;多副本部署必须共享同一 SECRET_KEY

九、参考链接