跳转至

部署

本文档同时覆盖两种部署方式:

  1. 本地/自建 VM 方式(Docker Compose)
  2. GitHub Actions CI/CD 方式(推送镜像 + 环境发布)

本地 Docker Compose(开发与联调)

项目包含两个 Docker Compose 文件,用于分离基础设施与应用服务。

首先启动基础设施服务:

just docker-infra

或手动执行:

docker network create chameleon-net
docker compose -f docker-compose.infra.yml up -d

然后启动应用服务:

docker compose up -d --build

默认端口映射:

  • AI 服务:8000
  • Demo 后端:3000
  • Demo 前端:80
  • Docs 站点:8001
  • MinIO:9000
  • MinIO Console:9001
  • Qdrant:6333
  • PostgreSQL:5432
  • pgAdmin:5050
  • RedisInsight:5540

中间件管理界面

各中间件提供 Web 管理界面,用于浏览和管理内部资源(数据库表、缓存键、向量集合、对象存储桶等)。

服务 地址 默认账号
pgAdmin(PostgreSQL) http://localhost:5050 邮箱:PGADMIN_DEFAULT_EMAIL(默认 admin@admin.com
密码:PGADMIN_DEFAULT_PASSWORD(默认 admin
RedisInsight(Redis) http://localhost:5540 无需登录
Qdrant Dashboard http://localhost:6333/dashboard 无需登录
MinIO Console http://localhost:9001 用户:MINIO_ROOT_USER
密码:MINIO_ROOT_PASSWORD

pgAdmin 连接 PostgreSQL

首次登录 pgAdmin 后,需手动添加服务器连接:

  • Hostpostgres
  • Port5432
  • Username.env 中的 POSTGRES_USER
  • Password.env 中的 POSTGRES_PASSWORD

RedisInsight 连接 Redis

首次打开 RedisInsight 后,手动添加 Redis 连接:

  • Hostredis
  • Port6379

容器间通信

  • ai-servicedemo-backend 通过外部网络 chameleon-net 通信。
  • 本地 docker-compose.yml 未包含 Admin 前端;但已包含 docs-site,默认映射到宿主机 8001 端口。
  • CI/CD 发布清单 docker-compose.deploy.yml 包含 admin-frontenddocs-site 服务。
  • docker-compose.deploy.yml 面向 Dokploy,使用内部网络 + expose,不对宿主机直接发布端口。
  • docker-compose.deploy.ymlai-service 使用命名卷 ai_service_data(而非 ./data/./config.toml/./resources bind),避免 Swarm 节点路径不存在导致任务被拒绝。
  • docker-compose.deploy.yml 已内置 Traefik labels;只需设置 BASE_DOMAIN 即可自动生成各服务路由,无需在 Dokploy 逐条手动配置。

Dokploy 单机部署(环境变量与 Domain)

以下变量用于 docker-compose.deploy.yml。在 Dokploy 中可配置在项目的 Environment Variables。

仓库已提供环境变量模板:

  • .env.deploy.example:用于 Dokploy staging/production
  • .env.local.example:用于本地开发
  • .env.example:模板入口说明文件

必填变量

变量名 用途 示例
AI_SERVICE_IMAGE ai-service 镜像地址 registry.zata.cafe/aibot/ai-service:<tag>
DEMO_BACKEND_IMAGE demo-backend 镜像地址 registry.zata.cafe/aibot/demo-backend:<tag>
DEMO_FRONTEND_IMAGE demo-frontend 镜像地址 registry.zata.cafe/aibot/demo-frontend:<tag>
ADMIN_FRONTEND_IMAGE admin-frontend 镜像地址 registry.zata.cafe/aibot/admin-frontend:<tag>
DOCS_SITE_IMAGE docs-site 镜像地址 registry.zata.cafe/aibot/docs-site:<tag>
BASE_DOMAIN 对外基础域名(自动派生 api/admin/docs 子域名) aibot.zata.cafe

强烈建议显式配置(不要使用默认值)

变量名 用途
POSTGRES_USER PostgreSQL 用户名
POSTGRES_PASSWORD PostgreSQL 密码
POSTGRES_DB PostgreSQL 数据库名
DATABASE_URL ai-service 数据库连接串,建议与上面三项保持一致
MINIO_ROOT_USER MinIO 管理账号
MINIO_ROOT_PASSWORD MinIO 管理密码
MINIO_ACCESS_KEY ai-service 访问 MinIO 的 Access Key,建议与 MINIO_ROOT_USER 一致
MINIO_SECRET_KEY ai-service 访问 MinIO 的 Secret Key,建议与 MINIO_ROOT_PASSWORD 一致
DASHSCOPE_API_KEY DashScope 模型调用密钥(如使用)
OPENROUTER_API_KEY OpenRouter 模型调用密钥(如使用)
APP_ENV 运行环境标识(stagingproduction
APP_INSTANCE 实例标识(例如 aibot-staging / aibot-prod
ENV_GUARD_MODE 环境保护模式(warnstrict
EXPECTED_PUBLIC_HOSTS 允许访问的外部 Host 白名单(推荐逗号分隔,也兼容 JSON 数组字符串)

可选变量

变量名 默认值 说明
DOKPLOY_PUBLIC_NETWORK dokploy-network Dokploy 外部网络名称
TRAEFIK_ENABLE true 是否启用 compose 内置 Traefik labels(true/false
TRAEFIK_CERT_RESOLVER letsencrypt Traefik ACME 证书解析器名称(需与 Dokploy/Traefik 实际 resolver 名称一致)
MINIO_ENDPOINT minio:9000 MinIO 容器内访问地址
MINIO_PUBLIC_ENDPOINT 预签名下载 URL 的对外访问地址(如 minio.example.comhttps://minio.example.com
MINIO_PUBLIC_SECURE false 对外下载地址是否使用 HTTPS
MINIO_PRESIGNED_EXPIRES_SECONDS 86400 预签名下载 URL 过期秒数(60 - 604800)
QDRANT_HOST qdrant Qdrant 容器名
QDRANT_PORT 6333 Qdrant 端口
CORS_ALLOWED_ORIGINS 本地开发地址列表 ai-service CORS 来源白名单(推荐逗号分隔,兼容 JSON 数组字符串,支持 *

环境隔离保护配置(推荐启用)

为避免 staging/prod 串环境(尤其同机部署场景),建议配置以下组合:

  • staging
  • APP_ENV=staging
  • APP_INSTANCE=aibot-staging
  • ENV_GUARD_MODE=warn
  • EXPECTED_PUBLIC_HOSTS=staging.aibot.zata.cafe,admin.staging.aibot.zata.cafe,api.staging.aibot.zata.cafe
  • production
  • APP_ENV=production
  • APP_INSTANCE=aibot-prod
  • ENV_GUARD_MODE=strict
  • EXPECTED_PUBLIC_HOSTS=aibot.zata.cafe,admin.aibot.zata.cafe,api.aibot.zata.cafe

说明:

  • strict 模式下,EXPECTED_PUBLIC_HOSTS 不能为空。
  • ai-service/healthz 会返回环境与数据库指纹信息,便于快速确认当前命中的环境。

ai-service 跨域访问配置

当你从其它域名(或本地开发域名)直接访问 https://api.<BASE_DOMAIN> 时,需要配置 CORS_ALLOWED_ORIGINS

示例(Dokploy 环境变量):

CORS_ALLOWED_ORIGINS=https://admin.aibot.zata.cafe,http://localhost:5173,http://127.0.0.1:5173

说明:

  • 使用逗号分隔多个来源,建议写完整 Origin(包含 http://https:// 及端口)。
  • 也兼容 JSON 数组字符串格式(例如 ["https://admin.aibot.zata.cafe","http://localhost:5173"])。
  • 若仅通过同域 /api 反向代理访问(例如 https://admin.<BASE_DOMAIN>/api/...),通常不需要额外 CORS 配置。
  • CORS_ALLOWED_ORIGINS=* 可放开所有来源,仅建议临时调试使用。

MinIO 下载链接配置(必看)

MINIO_ENDPOINTMINIO_PUBLIC_ENDPOINT 用途不同:

  • MINIO_ENDPOINTai-service 容器内访问 MinIO 的地址(通常固定 minio:9000
  • MINIO_PUBLIC_ENDPOINT:返回给浏览器访问的下载地址(外网可达域名)

场景 A:不想暴露 MinIO 域名(推荐)

  • 保持 MINIO_ENDPOINT=minio:9000
  • 不配置 MINIO_PUBLIC_ENDPOINT(留空)
  • 系统会自动回退为 API 代理下载链接(同域 /api/.../download

场景 B:希望浏览器直接访问 MinIO 预签名链接

  1. 在 Dokploy 给 minio 服务配置外网域名(例如 minio.aibot.zata.cafe,容器端口 9000
  2. ai-service 环境变量填写:
MINIO_ENDPOINT=minio:9000
MINIO_PUBLIC_ENDPOINT=https://minio.aibot.zata.cafe
MINIO_PUBLIC_SECURE=true
MINIO_PRESIGNED_EXPIRES_SECONDS=86400

注意:

  • 不要把 MINIO_ENDPOINT 改成公网域名;它应保持容器内地址。
  • MINIO_PUBLIC_ENDPOINT 已写 https://...MINIO_PUBLIC_SECURE 仍建议显式设为 true
  • 若下载链接仍出现 http://minio:9000/...,通常说明线上还在运行旧镜像/旧版本,需重新部署。

Traefik 路由(docker-compose.deploy.yml 内置)

设置 BASE_DOMAIN 后,Traefik 会自动创建以下路由:

Router Host Path Service Port
aibot-demo-frontend ${BASE_DOMAIN} / demo-frontend 80
aibot-demo-backend ${BASE_DOMAIN} /api(Strip /api demo-backend 3000
aibot-admin-frontend admin.${BASE_DOMAIN} / admin-frontend 80
aibot-admin-api admin.${BASE_DOMAIN} /api(Strip /api ai-service 8000
aibot-api api.${BASE_DOMAIN} / ai-service 8000
aibot-docs docs.${BASE_DOMAIN} / docs-site 80

注意事项:

  • 只需在 DNS 配置 4 个记录:${BASE_DOMAIN}admin.${BASE_DOMAIN}api.${BASE_DOMAIN}docs.${BASE_DOMAIN} 指向 Traefik 入口。
  • docker-compose.deploy.yml 已内置 HTTP (web) 到 HTTPS (websecure) 的 301 跳转;可直接访问 http://...,会自动跳转到 https://...
  • demo-backendaibot-admin-api 都会自动 Strip /api,避免后端路由出现前缀导致 404
  • 若想临时禁用内置 labels,可设置 TRAEFIK_ENABLE=false,改回在 Dokploy UI 手动配置。
  • 如需让浏览器可直接下载 MinIO 预签名链接,仍需单独配置 MINIO_PUBLIC_ENDPOINT(可选独立 MinIO 域名)。
  • 修改 BASE_DOMAIN 或 labels 后,需要重新部署 compose 才会生效。

使用自有 SSL 证书(替代 Dokploy Let's Encrypt)

若你已有商业证书或自签发证书,并希望在 Dokploy 中替代 Let's Encrypt,推荐使用以下方式。

  1. 在 Dokploy 的 Certificates 页面上传证书:
  2. Certificate Data:完整证书链(fullchain PEM)
  3. Private Key:私钥(PEM)
  4. 在本项目对应的 Dokploy 环境变量中设置:
  5. TRAEFIK_ENABLE=false
  6. 重新部署一次 Compose,让 docker-compose.deploy.yml 内置 Traefik labels 失效。
  7. 在 Dokploy 的 Domains 页面为各服务手动创建路由,并在 HTTPS 配置中选择你上传的证书:
  8. ${BASE_DOMAIN} -> demo-frontend:80,Path /
  9. ${BASE_DOMAIN} -> demo-backend:3000,Path /api(开启 Strip Prefix)
  10. admin.${BASE_DOMAIN} -> admin-frontend:80,Path /
  11. admin.${BASE_DOMAIN} -> ai-service:8000,Path /api(开启 Strip Prefix)
  12. api.${BASE_DOMAIN} -> ai-service:8000,Path /
  13. docs.${BASE_DOMAIN} -> docs-site:80,Path /
  14. 再次部署或应用 Domain 配置后,验证 https:// 访问是否命中自有证书。

注意:

  • 证书的 SAN/CN 必须覆盖以上域名(或使用可覆盖子域名的通配符证书)。
  • 若接入 Cloudflare,建议将 SSL/TLS 模式设置为 Full (strict),避免回源证书校验问题。

数据备份与恢复(PostgreSQL + MinIO + Qdrant)

仓库提供了统一脚本:

  • scripts/backup/backup_all.sh
  • scripts/backup/restore_all.sh

以及对应的 just 命令:

# 热备(建议每 15 分钟)
just backup-hot compose_file=docker-compose.deploy.yml

# 冷备(建议每天至少一次)
just backup-cold compose_file=docker-compose.deploy.yml

# 同时执行热备 + 冷备
just backup-all compose_file=docker-compose.deploy.yml

# 仅校验归档完整性(不执行恢复)
just backup-verify backup_id=<backup_id> compose_file=docker-compose.deploy.yml

# 恢复(逻辑恢复)
just restore backup_id=<backup_id> compose_file=docker-compose.deploy.yml

# 恢复(优先冷备快照)
just restore backup_id=<backup_id> prefer_cold=true compose_file=docker-compose.deploy.yml

备份归档路径

  • 临时目录:BACKUP_LOCAL_STAGE_DIR
  • 归档目录:BACKUP_LOCAL_ARCHIVE_DIR
  • 第二副本目录:BACKUP_REMOTE_DIR(可选)
  • 第二副本 rsync 目标:BACKUP_REMOTE_RSYNC_TARGET(可选)

建议将上述非敏感默认值写入 config.toml[backup];仅将敏感项(如 BACKUP_ENCRYPTION_PASSPHRASE)放在环境变量或密钥管理系统中。

远程复制触发条件(必须同时满足):

  1. skip_remote_copy = false
  2. 至少配置一个远程目标:remote_dirremote_rsync_target
  3. 备份流程本身成功

remote_dirremote_rsync_target 都为空,则只保留本地归档,不会推送第二副本。

默认归档命名格式:

  • aibot-backup-YYYYMMDDTHHMMSSZ.tar.gz
  • 启用加密时:aibot-backup-YYYYMMDDTHHMMSSZ.tar.gz.enc

调度建议(符合 1C 目标)

  • 热备:每 15 分钟一次
  • 冷备:每天一次(低峰时段)
  • 每月至少一次恢复演练,并记录 restore-report-*.json

示例 cron(Linux):

*/15 * * * * cd /opt/aibot && /usr/bin/just backup-hot compose_file=docker-compose.deploy.yml
20 3 * * * cd /opt/aibot && /usr/bin/just backup-cold compose_file=docker-compose.deploy.yml

恢复输出

恢复脚本会输出机器可读报告到 BACKUP_RESTORE_REPORT_DIR,包含:

  • 恢复耗时(duration_seconds
  • 是否满足 RTO <= 30 分钟
  • PostgreSQL / MinIO / Qdrant 的基础校验结果
  • 健康检查状态

自定义建议

  • 使用外部数据库时,请将 DATABASE_URL 指向托管实例。
  • 为模型 API Key 使用安全的密钥管理方案。
  • 多实例扩展场景下需确保 WebSocket 会话的一致性策略。
  • ai_service 生产镜像默认不安装 local_embedding 依赖组以减小体积;若需容器内本地 embedding,请在构建时设置 ENABLE_LOCAL_EMBEDDING=true

GitHub Actions CI/CD(staging 自动 + 生产 tag 发布)

工作流文件

  • CI:.github/workflows/ci.yml
  • CD:.github/workflows/cd.yml

触发规则

  • CI:对 main 的 Pull Request 自动触发
  • CD Staging 发布main 分支 push 自动触发
  • CD Production 发布:语义化 tag push 自动触发(匹配 v*,例如 v1.2.3
  • CD 回滚:手动触发 workflow_dispatch 并填写 rollback_tag(即历史 Git SHA 镜像标签)

镜像仓库与标签策略

  • Registry Host:registry.zata.cafe
  • 默认命名空间:aibot(可通过 GitHub Repository Variable IMAGE_NAMESPACE 覆盖)
  • 每个服务仅发布不可变标签:${GIT_SHA}(不再使用 main-latest

服务列表:

  • ai-service
  • demo-backend
  • demo-frontend
  • admin-frontend
  • docs-site
  • postgres
  • minio
  • qdrant
  • redis

必需 GitHub Secrets

Secret 用途 示例
REGISTRY_USERNAME 登录 registry.zata.cafe
REGISTRY_PASSWORD 登录 registry.zata.cafe
DOKPLOY_API_KEY Dokploy API Key(用于更新 compose 环境变量)
DOKPLOY_STAGING_DEPLOY_HOOK staging 发布 webhook
DOKPLOY_PROD_DEPLOY_HOOK 生产环境发布 webhook(手动审批后触发) http://zata.cafe:3000/api/deploy/compose/lFyKFKfELXOthpMRsNaea
PROD_HEALTHCHECK_URL 生产发布后健康检查地址 https://admin.aibot.zata.cafe/api/healthzhttps://api.aibot.zata.cafe/healthz

可选 Secret:

Secret 用途 示例
DOKPLOY_API_BASE_URL Dokploy API 基础地址兜底值(例如 https://dokploy.example.com/api
DOKPLOY_STAGING_COMPOSE_ID staging compose ID 兜底值(当 deploy hook 最后一段不是 composeId 时使用)
DOKPLOY_PROD_COMPOSE_ID production compose ID 兜底值(当 deploy hook 最后一段不是 composeId 时使用)

说明:workflow 会优先从 DOKPLOY_*_DEPLOY_HOOK 自动解析 composeId 与 API base(.../api);若你的 Dokploy 返回 Compose not found,通常表示 hook 最后一段是 deploy token 而不是 composeId,这时请显式配置 DOKPLOY_STAGING_COMPOSE_ID / DOKPLOY_PROD_COMPOSE_ID

环境门禁(Manual Approval)

  • 在 GitHub 仓库创建 Environments:
  • staging
  • production
  • production 配置 Required reviewers,即可在 CD 中实现人工审批后发布。

Staging 发布流程(main push)

  1. 构建并推送 5 个服务镜像的不可变标签 ${GIT_SHA}registry.zata.cafe
  2. 调用 Dokploy API,将 staging compose 的 *_IMAGE 更新为 ${GIT_SHA}
  3. 触发 DOKPLOY_STAGING_DEPLOY_HOOK

说明:若 docs-site:${GIT_SHA} 不存在,发布流程会保持当前 DOCS_SITE_IMAGE,不阻塞核心服务发布。

Production 发布流程(tag push)

  1. 创建并推送符合 v* 的 Git tag(例如 v1.2.3
  2. CD 构建并推送 5 个服务镜像的不可变标签 ${GIT_SHA}registry.zata.cafe
  3. 进入 production 环境审批
  4. 审批通过后,调用 Dokploy API,将 production compose 的 *_IMAGE 更新为 ${GIT_SHA}
  5. 触发 DOKPLOY_PROD_DEPLOY_HOOK
  6. 轮询 PROD_HEALTHCHECK_URL 直到通过或超时失败

回滚流程

方案 A:GitHub Actions 手动回滚(推荐)

  1. 打开 CD workflow 的 Run workflow
  2. 填写 rollback_tag(目标历史 SHA)
  3. 流程会通过 Dokploy API 将 production compose 的 *_IMAGE 更新为 ${rollback_tag}
  4. 自动触发生产 deploy hook 并执行健康检查

说明:若该历史 tag 尚未包含 docs-site 镜像,回滚流程会保留现有 DOCS_SITE_IMAGE,不会阻塞其他核心服务回滚。

方案 B:VM 上执行 Compose 回滚脚本

仓库内提供脚本:

  • scripts/deploy/compose-deploy.sh
  • scripts/deploy/compose-rollback.sh

示例(部署指定 SHA):

IMAGE_TAG=<git_sha> ./scripts/deploy/compose-deploy.sh \
  --registry-host registry.zata.cafe \
  --image-namespace aibot

示例(回滚到上一个成功版本):

./scripts/deploy/compose-rollback.sh

脚本会在 .deploy-state/ 记录 current.env / previous.env,用于快速恢复。 若目标回滚元数据缺失 DOCS_SITE_IMAGE 或对应镜像不可用,脚本会优先保留当前 docs 镜像,不可用时再回退到 docs-site:main-latest(仅脚本模式使用)。