部署
本文档同时覆盖两种部署方式:
- 本地/自建 VM 方式(Docker Compose)
- 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 后,需手动添加服务器连接:
- Host:
postgres - Port:
5432 - Username:
.env中的POSTGRES_USER - Password:
.env中的POSTGRES_PASSWORD
RedisInsight 连接 Redis
首次打开 RedisInsight 后,手动添加 Redis 连接:
- Host:
redis - Port:
6379
容器间通信
ai-service与demo-backend通过外部网络chameleon-net通信。- 本地
docker-compose.yml未包含 Admin 前端;但已包含docs-site,默认映射到宿主机8001端口。 - CI/CD 发布清单
docker-compose.deploy.yml包含admin-frontend与docs-site服务。 docker-compose.deploy.yml面向 Dokploy,使用内部网络 +expose,不对宿主机直接发布端口。docker-compose.deploy.yml中ai-service使用命名卷ai_service_data(而非./data/./config.toml/./resourcesbind),避免 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 |
运行环境标识(staging 或 production) |
APP_INSTANCE |
实例标识(例如 aibot-staging / aibot-prod) |
ENV_GUARD_MODE |
环境保护模式(warn 或 strict) |
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.com 或 https://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=stagingAPP_INSTANCE=aibot-stagingENV_GUARD_MODE=warnEXPECTED_PUBLIC_HOSTS=staging.aibot.zata.cafe,admin.staging.aibot.zata.cafe,api.staging.aibot.zata.cafe- production
APP_ENV=productionAPP_INSTANCE=aibot-prodENV_GUARD_MODE=strictEXPECTED_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_ENDPOINT 和 MINIO_PUBLIC_ENDPOINT 用途不同:
MINIO_ENDPOINT:ai-service容器内访问 MinIO 的地址(通常固定minio:9000)MINIO_PUBLIC_ENDPOINT:返回给浏览器访问的下载地址(外网可达域名)
场景 A:不想暴露 MinIO 域名(推荐)
- 保持
MINIO_ENDPOINT=minio:9000 - 不配置
MINIO_PUBLIC_ENDPOINT(留空) - 系统会自动回退为 API 代理下载链接(同域
/api/.../download)
场景 B:希望浏览器直接访问 MinIO 预签名链接
- 在 Dokploy 给
minio服务配置外网域名(例如minio.aibot.zata.cafe,容器端口9000) 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-backend和aibot-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,推荐使用以下方式。
- 在 Dokploy 的 Certificates 页面上传证书:
Certificate Data:完整证书链(fullchain PEM)Private Key:私钥(PEM)- 在本项目对应的 Dokploy 环境变量中设置:
TRAEFIK_ENABLE=false- 重新部署一次 Compose,让
docker-compose.deploy.yml内置 Traefik labels 失效。 - 在 Dokploy 的 Domains 页面为各服务手动创建路由,并在 HTTPS 配置中选择你上传的证书:
${BASE_DOMAIN}->demo-frontend:80,Path/${BASE_DOMAIN}->demo-backend:3000,Path/api(开启 Strip Prefix)admin.${BASE_DOMAIN}->admin-frontend:80,Path/admin.${BASE_DOMAIN}->ai-service:8000,Path/api(开启 Strip Prefix)api.${BASE_DOMAIN}->ai-service:8000,Path/docs.${BASE_DOMAIN}->docs-site:80,Path/- 再次部署或应用 Domain 配置后,验证
https://访问是否命中自有证书。
注意:
- 证书的 SAN/CN 必须覆盖以上域名(或使用可覆盖子域名的通配符证书)。
- 若接入 Cloudflare,建议将 SSL/TLS 模式设置为
Full (strict),避免回源证书校验问题。
数据备份与恢复(PostgreSQL + MinIO + Qdrant)
仓库提供了统一脚本:
scripts/backup/backup_all.shscripts/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)放在环境变量或密钥管理系统中。
远程复制触发条件(必须同时满足):
skip_remote_copy = false- 至少配置一个远程目标:
remote_dir或remote_rsync_target - 备份流程本身成功
若 remote_dir 和 remote_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 VariableIMAGE_NAMESPACE覆盖) - 每个服务仅发布不可变标签:
${GIT_SHA}(不再使用main-latest)
服务列表:
ai-servicedemo-backenddemo-frontendadmin-frontenddocs-sitepostgresminioqdrantredis
必需 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/healthz 或 https://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:
stagingproduction- 为
production配置 Required reviewers,即可在 CD 中实现人工审批后发布。
Staging 发布流程(main push)
- 构建并推送 5 个服务镜像的不可变标签
${GIT_SHA}到registry.zata.cafe - 调用 Dokploy API,将 staging compose 的
*_IMAGE更新为${GIT_SHA} - 触发
DOKPLOY_STAGING_DEPLOY_HOOK
说明:若 docs-site:${GIT_SHA} 不存在,发布流程会保持当前 DOCS_SITE_IMAGE,不阻塞核心服务发布。
Production 发布流程(tag push)
- 创建并推送符合
v*的 Git tag(例如v1.2.3) - CD 构建并推送 5 个服务镜像的不可变标签
${GIT_SHA}到registry.zata.cafe - 进入
production环境审批 - 审批通过后,调用 Dokploy API,将 production compose 的
*_IMAGE更新为${GIT_SHA} - 触发
DOKPLOY_PROD_DEPLOY_HOOK - 轮询
PROD_HEALTHCHECK_URL直到通过或超时失败
回滚流程
方案 A:GitHub Actions 手动回滚(推荐)
- 打开
CDworkflow 的Run workflow - 填写
rollback_tag(目标历史 SHA) - 流程会通过 Dokploy API 将 production compose 的
*_IMAGE更新为${rollback_tag} - 自动触发生产 deploy hook 并执行健康检查
说明:若该历史 tag 尚未包含 docs-site 镜像,回滚流程会保留现有 DOCS_SITE_IMAGE,不会阻塞其他核心服务回滚。
方案 B:VM 上执行 Compose 回滚脚本
仓库内提供脚本:
scripts/deploy/compose-deploy.shscripts/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(仅脚本模式使用)。