部署
本文档同时覆盖两种部署方式:
- 本地/自建 VM 方式(Docker Compose)
- GitHub Actions CI/CD 方式(推送镜像 + 环境发布)
本地 Docker Compose(开发与联调)
项目默认使用仓库根目录 docker-compose.yml 一次性启动应用与基础设施,服务间通过内部容器网络通信。
启动:
docker compose up -d --build
如果你希望在本地联调时自动注入一批可重复执行的 mock 数据(Agent / Knowledge Source / Chunk / Session / Evaluation Dataset),可直接运行:
bash scripts/docker_up_with_mock_data.sh
如果容器已经启动,只需要重新注入 mock 数据:
bash scripts/docker_up_with_mock_data.sh --skip-up
默认端口映射:
- AI 服务:18000
- Demo 后端:13000
- Demo 前端:18080
- Admin 前端:18081
- Docs 站点:18001
说明:
postgres、redis、qdrant、minio在docker-compose.yml中默认仅容器内可访问,不直接暴露宿主机端口。- 如果你需要中间件管理界面(pgAdmin、RedisInsight、MinIO Console),可额外启动
docker-compose.infra.yml。
可选:中间件管理界面(docker-compose.infra.yml)
启动(仅中间件与管理界面):
just docker-infra
或:
docker compose -f docker-compose.infra.yml up -d
多 worktree 使用约定
本地开发相关的 just 命令与辅助脚本,现统一使用固定的 Docker Compose project name:
${COMPOSE_PROJECT_NAME:-aibot}
这意味着:
- 你可以在主仓目录执行
just docker-infra,再进入任意git worktree目录执行just demo-up。 - 也可以直接在任意 worktree 目录执行
just docker-infra/just demo-up,它们都会操作同一套本地 Compose 容器。 - 不会再因为当前目录名不同,导致
docker compose ps检查不到已经运行的基础设施容器。
默认情况下,project name 为 aibot。如果你确实需要在同一台机器上启动一套完全隔离的本地容器,可临时覆盖环境变量:
export COMPOSE_PROJECT_NAME=aibot-exp
just docker-infra
just demo-up
这样会创建一套新的本地 Compose 项目,与默认的 aibot 容器彼此隔离。
各中间件提供 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
容器间通信
- 本地
docker-compose.yml使用内部 bridge 网络aibot-internal,无需预先创建外部网络。 - 本地
docker-compose.yml已包含admin-frontend与docs-site。 demo-frontend通过容器内代理将/api与/api/socket.io一并转发到demo-backend。admin-frontend通过容器内代理将/api转发到ai-service,并在同一 host 下直接以根路径/提供 Studio SPA;旧的/studio/*链接会自动重定向到对应根路径。demo-frontend与admin-frontend的生产镜像都会内置各自的nginx.conf;这样 Dokploy/Traefik 直连镜像时,SPA 深链接刷新仍会回退到index.html,而不是返回404。- 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 逐条手动配置。- 若通过 Admin Frontend 配置
stdioMCP Server,命令会在ai-service容器内执行;仓库内脚本应使用容器路径/app/scripts/...,不要使用开发机路径/home/atahang/codes/aibot/...。
Dokploy 上为 ai-service 添加本地 stdio MCP 脚本
如果你要在 Dokploy 上把仓库内脚本挂成 stdio MCP Server,例如:
/app/scripts/dewell_tracking_stdio.py/app/scripts/mock_mcp_stdio.py
请按以下规则填写 Admin Frontend 的 MCP Servers 表单:
Transport:stdioCommand:uvArgs:run python /app/scripts/dewell_tracking_stdio.py
或:
Transport:stdioCommand:uvArgs:run python /app/scripts/mock_mcp_stdio.py
注意:
Args字段是普通文本,不是 JSON 数组输入框。- Admin Frontend 会把
Args按空格拆成数组后再发送给后端。 - 因此不要填写
["run", "python", "/app/scripts/dewell_tracking_stdio.py"]这种 JSON 字符串。 - 如果填成 JSON 字符串,
ai-service会把整串视为单个参数,测试时通常会报: No such file or directory: '[\"run\", \"python\", ...]'
推荐创建后立即执行:
Health CheckTest Call -> tools/list- 对
dewell_tracking_stdio.py再执行一次tools/call,传入selectNo
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> |
| OUTLOOK_ADDIN_IMAGE | outlook-addin 镜像地址 | registry.zata.cafe/aibot/outlook-addin:<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) |
DB_MIGRATION_MODE |
数据库迁移策略(当前发布链路建议 auto,由 ai-service 启动阶段执行迁移) |
APP_INSTANCE |
实例标识(例如 aibot-staging / aibot-prod;在 docker-compose.deploy.yml 中同时作为 Traefik 对象命名空间) |
ENV_GUARD_MODE |
环境保护模式(warn 或 strict) |
EXPECTED_PUBLIC_HOSTS |
允许访问的外部 Host 白名单(推荐逗号分隔,也兼容 JSON 数组字符串) |
ADMIN_ACCESS_TOKEN_SECRET_KEY |
管理后台 access/refresh/challenge token 的签名密钥;生产环境必填,不能使用默认占位值 |
ADMIN_ACCESS_COOKIE_SECURE |
管理后台 Cookie 是否仅允许 HTTPS 传输;生产环境必须为 true |
ADMIN_ACCESS_BOOTSTRAP_EMAIL |
首次部署时 bootstrap super_admin 的邮箱;仅在当前数据库里管理员总数为 0 时生效 |
ADMIN_ACCESS_BOOTSTRAP_USERNAME |
首次部署时 bootstrap super_admin 的用户名;需与 email/password 一起配置 |
ADMIN_ACCESS_BOOTSTRAP_PASSWORD |
首次部署时 bootstrap super_admin 的初始密码;需与 email/username 一起配置 |
可选变量
| 变量名 | 默认值 | 说明 |
|---|---|---|
DOKPLOY_PUBLIC_NETWORK |
dokploy-network |
Dokploy 外部网络名称 |
TRAEFIK_ENABLE |
true |
是否启用 compose 内置 Traefik labels(true/false) |
TRAEFIK_CERT_RESOLVER |
letsencrypt |
Traefik ACME 证书解析器名称(需与 Dokploy/Traefik 实际 resolver 名称一致) |
ADMIN_SURFACES_ENABLE |
false |
是否启用受保护的共享中间件管理界面(pgAdmin / MinIO Console / RedisInsight / Qdrant Dashboard) |
OPS_AUTH_HTPASSWD |
示例占位值 | 共享管理界面的 Traefik BasicAuth 哈希(建议 htpasswd -nbB 生成后写入) |
MINIO_ENDPOINT |
minio:9000 |
MinIO 容器内访问地址 |
MINIO_BUCKET_RAW_DOCUMENTS |
raw-documents |
共享只读 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) |
MINIO_TEAM_READONLY_USER |
空 | 可选的共享 MinIO Console 只读账号;为空时直接使用 root 账号登录 |
MINIO_TEAM_READONLY_PASSWORD |
空 | 可选的共享 MinIO Console 只读账号密码 |
QDRANT_HOST |
qdrant |
Qdrant 容器名 |
QDRANT_PORT |
6333 |
Qdrant 端口 |
QDRANT_API_KEY |
空 | ai-service 访问 Qdrant 的 API Key;当前 compose 不会仅因设置它就自动开启 Qdrant 服务级鉴权 |
RAG_TOP_K |
空 | 覆盖 config.toml [rag].top_k;注意变量名必须是 RAG_TOP_K,不是 top_k |
RAG_SCORE_THRESHOLD |
空 | 覆盖 config.toml [rag].score_threshold |
LANGFUSE_ENABLED |
false |
是否启用 ai-service 的 Langfuse trace 导出 |
LANGFUSE_PUBLIC_KEY |
空 | Langfuse 公钥 |
LANGFUSE_SECRET_KEY |
空 | Langfuse 密钥 |
LANGFUSE_HOST |
https://us.cloud.langfuse.com |
Langfuse 服务地址 |
LANGFUSE_TRACING_ENVIRONMENT |
跟随 APP_ENV |
写入 Langfuse 的环境标签;CD 默认会为 staging/production 显式写入 |
LANGFUSE_RELEASE |
空 | 可选发布标签;CD 默认会写入 staging=GITHUB_SHA、production=GITHUB_REF_NAME |
LANGFUSE_SAMPLE_RATE |
1.0 |
Langfuse 采样率 |
RELEASE_PUBLISHED_AT |
空 | Admin 首页 “About” 对话框使用的发布时间;CD 默认在每次 deploy/rollback 时写入 UTC 时间 |
RELEASE_COMMIT_SHA |
空 | 测试版 About 对话框显示的 Git SHA;CD 默认在 staging/test deploy 时写入,也会在 rollback 时写入回滚目标 SHA |
RELEASE_COMMIT_SUBJECT |
空 | 测试版 About 对话框显示的单行 commit 摘要;CD 默认在 staging/test deploy 时写入 |
RELEASE_TAG_NAME |
空 | 生产版 About 对话框显示的 tag 名称;CD 默认在 production deploy 时写入 GITHUB_REF_NAME,rollback 时写入目标 immutable tag |
RELEASE_TAG_MESSAGE |
空 | 生产版 About 对话框显示的 tag 注释/发布摘要;CD 默认在 production deploy 时写入 |
ADMIN_ACCESS_COOKIE_DOMAIN |
空 | 管理后台 Cookie Domain;默认留空,让浏览器按当前 Host 作用域保存 |
ADMIN_ACCESS_BOOTSTRAP_DISPLAY_NAME |
Initial Super Admin |
首个 bootstrap super_admin 的展示名称 |
PGADMIN_DEFAULT_EMAIL |
ops@example.com |
pgAdmin 登录邮箱 |
PGADMIN_DEFAULT_PASSWORD |
空 | pgAdmin 登录密码 |
PGADMIN_TEAM_SERVER_NAME |
Shared PostgreSQL |
pgAdmin 默认预置服务器名称 |
PGADMIN_TEAM_DB_HOST |
postgres |
pgAdmin 默认预置数据库主机 |
PGADMIN_TEAM_DB_PORT |
5432 |
pgAdmin 默认预置数据库端口 |
PGADMIN_TEAM_DB_NAME |
空 | pgAdmin 默认预置数据库名称;为空时回退到 POSTGRES_DB |
PGADMIN_TEAM_DB_USER |
空 | pgAdmin 默认预置数据库用户;为空时回退到 POSTGRES_USER |
PGADMIN_TEAM_DB_PASSWORD |
空 | pgAdmin 默认预置数据库密码;为空时回退到 POSTGRES_PASSWORD |
REDIS_DEFAULT_PASSWORD |
空 | Redis 默认管理员账号(default 用户)密码,仅管理员持有 |
REDIS_TEAM_USERNAME |
team_readonly |
RedisInsight 手工连接时使用的只读 ACL 用户名 |
REDIS_TEAM_PASSWORD |
空 | RedisInsight 手工连接时使用的只读 ACL 用户密码 |
CORS_ALLOWED_ORIGINS |
本地开发地址列表 | ai-service CORS 来源白名单(推荐逗号分隔,兼容 JSON 数组字符串,支持 *) |
注意:
- Dokploy 项目里即使已经声明了
ADMIN_ACCESS_*变量,docker-compose.deploy.yml也必须把它们显式写进ai-service.environment,否则容器内看不到这些值。 RAG_TOP_K/RAG_SCORE_THRESHOLD也是同样的规则:仅在 Dokploy 项目里声明变量还不够,docker-compose.deploy.yml里必须显式透传到ai-service.environment。- 如果你在 Dokploy 里写的是
top_k=10,当前代码不会读取;RAG 运行时读取的是RAG_TOP_K=10。 - 目前
docker-compose.deploy.yml已显式透传主要的CHAT_MODEL_*、MINIO_*、QDRANT_*、EMBEDDING_*、RERANK_*、MULTI_QUERY_*、RAG_*、CHUNK_*、TIMEOUT_*、MCP_*、SKILL_*、SCHEDULED_TASKS_*、RUNTIME_CHECKPOINT_*、AGENT_MEMORY_*、LANGFUSE_*、ADMIN_ACCESS_*、AGENT_*、ETL_*、DOC_EXTRACTION_*配置。 - 如果后续在
ai_service/utils/settings.py新增了新的环境变量前缀,部署清单也应同步补充;否则 Dokploy 项目里即使配置了变量,容器内仍然读不到。 - 如果启动日志出现
Admin access token secret was not configured explicitly; using an ephemeral runtime secret,或首次POST /admin/auth/login持续返回401,优先检查上面的ADMIN_ACCESS_*变量是否已经实际注入到ai-service容器。
环境隔离保护配置(推荐启用)
为避免 staging/prod 串环境(尤其同机部署场景),建议配置以下组合:
- staging
APP_ENV=stagingDB_MIGRATION_MODE=autoAPP_INSTANCE=aibot-stagingENV_GUARD_MODE=warnEXPECTED_PUBLIC_HOSTS=staging.aibot.zata.cafe,admin.staging.aibot.zata.cafe,api.staging.aibot.zata.cafe,outlook.staging.aibot.zata.cafe,docs.staging.aibot.zata.cafe- production
APP_ENV=productionDB_MIGRATION_MODE=autoAPP_INSTANCE=aibot-prodENV_GUARD_MODE=strictEXPECTED_PUBLIC_HOSTS=aibot.zata.cafe,admin.aibot.zata.cafe,api.aibot.zata.cafe,outlook.aibot.zata.cafe,docs.aibot.zata.cafe
Outlook Add-in 发布补充
outlook-addin 现在作为独立静态站点部署,推荐域名如下:
- staging:
https://outlook.staging.aibot.zata.cafe - production:
https://outlook.aibot.zata.cafe
对应约束:
- add-in 宿主必须通过 HTTPS 对外提供,否则 Outlook 宿主无法稳定加载。
outlook-addin前端请求使用同源/api/*,因此outlook.<BASE_DOMAIN>上的/api必须由网关路由到demo-backend。manifest.xml由容器启动时基于OUTLOOK_ADDIN_HOST_URL运行时生成,因此 staging / production 必须显式提供各自宿主地址。- 宿主需要直接暴露
/manifest.xml。
EXE 发布资产
生产 tag 发布时,GitHub Actions 会额外生成以下 Release 附件:
outlook-addin-installer-production.exeoutlook-addin-installer-production.sha256outlook-addin-manifest-production.xml
其中:
.exe是 Windows 安装助手,会下载对应版本的 manifest 并打开下载说明页。- 文档站通过 GitHub Release 的
latest/download稳定链接暴露下载入口。 - 当前阶段不自动处理 Microsoft 365 组织分发。
说明:
strict模式下,EXPECTED_PUBLIC_HOSTS不能为空。ai-service的/healthz会返回环境与数据库指纹信息,便于快速确认当前命中的环境。- Admin Dashboard 首页的 “About” 对话框会调用
ai-service的/release-metadata,优先展示这里的RELEASE_*环境变量;若部分字段为空,会回退到LANGFUSE_RELEASE作为标识兜底。 - 当前发布链路不在 GitHub Actions 中单独执行 Alembic 迁移,而是由部署后的
ai-service在DB_MIGRATION_MODE=auto下启动时执行迁移。
同机部署 staging + production(共享 Traefik)
若 staging 与 production 运行在同一个 Dokploy / Traefik Docker provider 下,仅修改 BASE_DOMAIN 仍然不够。
原因:
- Traefik 会把 Docker provider 中的
routers/services/middlewares名称放在同一命名空间。 - 若两套 compose 都生成同名对象(例如
aibot-api、aibot-demo-backend),即使域名规则不同,也会被判定为配置冲突。
从当前版本开始,docker-compose.deploy.yml 使用 APP_INSTANCE 为所有 Traefik 对象加前缀,例如:
- staging:
APP_INSTANCE=aibot-staging aibot-staging-apiaibot-staging-demo-backendaibot-staging-admin-frontend- production:
APP_INSTANCE=aibot-prod aibot-prod-apiaibot-prod-demo-backendaibot-prod-admin-frontend
同机部署的最小要求:
BASE_DOMAIN不同。APP_INSTANCE不同。EXPECTED_PUBLIC_HOSTS不重叠。- 修改后重新部署 compose,让 Traefik 重新加载 labels。
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/...,通常说明线上还在运行旧镜像/旧版本,需重新部署。
受保护的共享中间件管理界面
从当前版本开始,docker-compose.deploy.yml 支持把本地 docker-compose.infra.yml 中的管理界面模式带到 Dokploy,但默认关闭,避免现有环境升级时意外暴露新入口。
最小可复制配置(无需先进入宿主机):
ADMIN_SURFACES_ENABLE=true
OPS_AUTH_HTPASSWD=ops:$$2y$$05$$replace-with-your-real-hash
PGADMIN_DEFAULT_EMAIL=ops@example.com
PGADMIN_DEFAULT_PASSWORD=<strong-password>
REDIS_DEFAULT_PASSWORD=<admin-only-strong-password>
REDIS_TEAM_USERNAME=team_readonly
REDIS_TEAM_PASSWORD=<strong-password>
# 先留空即可;只有你后续想切到服务原生只读账号时才需要再填
PGADMIN_TEAM_DB_NAME=
PGADMIN_TEAM_DB_USER=
PGADMIN_TEAM_DB_PASSWORD=
MINIO_TEAM_READONLY_USER=
MINIO_TEAM_READONLY_PASSWORD=
这组变量配合你原本已有的 POSTGRES_*、MINIO_ROOT_*、BASE_DOMAIN 即可让 4 个界面在重部署后直接可用,不需要先进入 Dokploy 宿主机。默认行为是:
pgAdmin自动回退到POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DBMinIO Console直接使用现有MINIO_ROOT_USER/MINIO_ROOT_PASSWORDRedisInsight使用REDIS_TEAM_USERNAME/REDIS_TEAM_PASSWORDQdrant Dashboard仅通过 Traefik 外层 BasicAuth 保护
启用步骤:
- 在 Dokploy 环境变量中加入或覆盖上面的最小配置:
ADMIN_SURFACES_ENABLE=true
OPS_AUTH_HTPASSWD=ops:$$2y$$05$$replace-with-your-real-hash
PGADMIN_DEFAULT_EMAIL=ops@example.com
PGADMIN_DEFAULT_PASSWORD=<strong-password>
REDIS_DEFAULT_PASSWORD=<admin-only-strong-password>
REDIS_TEAM_USERNAME=team_readonly
REDIS_TEAM_PASSWORD=<strong-password>
PGADMIN_TEAM_DB_NAME=
PGADMIN_TEAM_DB_USER=
PGADMIN_TEAM_DB_PASSWORD=
MINIO_TEAM_READONLY_USER=
MINIO_TEAM_READONLY_PASSWORD=
-
为以下 4 个域名添加 DNS 记录,指向 Dokploy / Traefik 入口:
-
db.${BASE_DOMAIN} minio-console.${BASE_DOMAIN}redis.${BASE_DOMAIN}-
qdrant.${BASE_DOMAIN} -
重新部署
docker-compose.deploy.yml。
说明:
- 重新部署后,无需进入 Dokploy 宿主机即可直接访问这 4 个管理界面。
pgAdmin会自动预置一条数据库连接:- 若
PGADMIN_TEAM_DB_*留空,则直接回退到POSTGRES_USER/POSTGRES_PASSWORD/POSTGRES_DB - 若你后续希望改成只读角色,再单独配置
PGADMIN_TEAM_DB_*并执行可选 bootstrap MinIO Console若MINIO_TEAM_READONLY_*留空,直接使用MINIO_ROOT_USER/MINIO_ROOT_PASSWORD登录即可。- Redis 不再依赖单独 bootstrap:当
ADMIN_SURFACES_ENABLE=true时,redis容器会在启动时基于REDIS_DEFAULT_PASSWORD、REDIS_TEAM_USERNAME、REDIS_TEAM_PASSWORD渲染 ACL 用户。 - Qdrant 当前默认只通过 Traefik 外层鉴权保护 dashboard;
QDRANT_API_KEY仅用于在你单独开启 Qdrant 服务级鉴权后,让ai-service使用同一凭据。 OPS_AUTH_HTPASSWD建议用htpasswd -nbB生成,并在写入 compose 环境时将$转义为$$。
可选增强:
- 如果你后续拿到了 Dokploy 宿主机或 Docker CLI 访问能力,并且希望把 PostgreSQL / MinIO 切成共享只读账号模式,可再执行:
bash scripts/deploy/bootstrap_admin_surfaces.sh --project-name <compose-project-name>
- 该脚本会创建:
- PostgreSQL 只读角色(供 pgAdmin 使用)
- MinIO 只读用户
- 若当前宿主机上同时运行多套同类 compose 工程,请显式传入
--project-name;如果只有一套匹配中的服务容器,脚本也可以自动识别。
访问方式:
https://db.${BASE_DOMAIN}- 先经过 Traefik BasicAuth
- 再使用
PGADMIN_DEFAULT_EMAIL/PGADMIN_DEFAULT_PASSWORD登录 pgAdmin - 页面会自动预置一条 PostgreSQL 连接
- 默认回退到
POSTGRES_USER/POSTGRES_PASSWORD - 若你后续执行了可选 bootstrap 并配置
PGADMIN_TEAM_DB_*,也可以切成只读角色 https://minio-console.${BASE_DOMAIN}- 先经过 Traefik BasicAuth
- 默认直接使用
MINIO_ROOT_USER/MINIO_ROOT_PASSWORD登录 MinIO Console - 若你后续执行了可选 bootstrap 并配置
MINIO_TEAM_READONLY_*,也可以切成只读用户 https://redis.${BASE_DOMAIN}- 先经过 Traefik BasicAuth
- 首次进入 RedisInsight 后,手工添加连接:
- Host:
redis - Port:
6379 - Username:
REDIS_TEAM_USERNAME - Password:
REDIS_TEAM_PASSWORD
- Host:
- 管理员若需要写权限,使用 Redis
default用户和REDIS_DEFAULT_PASSWORD https://qdrant.${BASE_DOMAIN}/dashboard- 先经过 Traefik BasicAuth
- 当前 compose 默认不启用 Qdrant 服务级 API key;若你在仓库外单独开启它,也需要同步给
ai-service配置QDRANT_API_KEY
运维建议:
ADMIN_SURFACES_ENABLE默认保持false,仅在 DNS 与凭据都准备好后再开启。- 团队共享访问默认使用只读凭据;写权限凭据仅保留给管理员。
- 若当前无法进入 Dokploy 宿主机,先用受保护界面 + 管理员凭据是可行的;只读账号初始化属于后续增强,不再是启用界面的前置步骤。
- 若 staging 与 production 同机部署,两套环境都可以启用相同的管理界面模式,但仍需使用不同的
APP_INSTANCE和各自独立的凭据。
Traefik 路由(docker-compose.deploy.yml 内置)
设置 BASE_DOMAIN 与唯一的 APP_INSTANCE 后,Traefik 会自动创建以下路由:
| Router | Host | Path | Service | Port |
|---|---|---|---|---|
${APP_INSTANCE}-demo-frontend |
${BASE_DOMAIN} |
/ |
demo-frontend |
80 |
${APP_INSTANCE}-demo-backend |
${BASE_DOMAIN} |
/api(Strip /api) |
demo-backend |
3000 |
${APP_INSTANCE}-admin-frontend |
admin.${BASE_DOMAIN} |
/ |
admin-frontend |
80 |
${APP_INSTANCE}-admin-api |
admin.${BASE_DOMAIN} |
/api(Strip /api) |
ai-service |
8000 |
${APP_INSTANCE}-api |
api.${BASE_DOMAIN} |
/ |
ai-service |
8000 |
${APP_INSTANCE}-outlook-api |
outlook.${BASE_DOMAIN} |
/api(Strip /api) |
demo-backend |
3000 |
${APP_INSTANCE}-outlook-addin |
outlook.${BASE_DOMAIN} |
/ |
outlook-addin |
80 |
${APP_INSTANCE}-docs |
docs.${BASE_DOMAIN} |
/ |
docs-site |
80 |
${APP_INSTANCE}-pgadmin |
db.${BASE_DOMAIN} |
/ |
pgadmin |
80 |
${APP_INSTANCE}-minio-console |
minio-console.${BASE_DOMAIN} |
/ |
minio |
9001 |
${APP_INSTANCE}-redisinsight |
redis.${BASE_DOMAIN} |
/ |
redisinsight |
5540 |
${APP_INSTANCE}-qdrant |
qdrant.${BASE_DOMAIN} |
/ |
qdrant |
6333 |
注意事项:
- 基础应用只需 5 个 DNS 记录:
${BASE_DOMAIN}、admin.${BASE_DOMAIN}、api.${BASE_DOMAIN}、outlook.${BASE_DOMAIN}、docs.${BASE_DOMAIN}指向 Traefik 入口。 - 若启用
ADMIN_SURFACES_ENABLE=true,还需额外增加:db.${BASE_DOMAIN}、minio-console.${BASE_DOMAIN}、redis.${BASE_DOMAIN}、qdrant.${BASE_DOMAIN}。 docker-compose.deploy.yml已内置 HTTP (web) 到 HTTPS (websecure) 的 301 跳转;可直接访问http://...,会自动跳转到https://...。demo-backend和${APP_INSTANCE}-admin-api都会自动 Strip/api,避免后端路由出现前缀导致404。- Demo 站点的 realtime websocket 也通过
${APP_INSTANCE}-demo-backend这条/api路由进入demo-backend,再由其桥接到ai-service;不再为${BASE_DOMAIN}暴露单独的 ai-service websocket ingress。 - 在
admin.${BASE_DOMAIN}域名下查看后端 Swagger / ReDoc 时,使用https://admin.${BASE_DOMAIN}/api/docs与https://admin.${BASE_DOMAIN}/api/redoc;后端会按反向代理前缀生成/api/openapi.json。 - 4 个共享中间件管理界面都会额外挂载
${APP_INSTANCE}-ops-authBasicAuth 中间件。 admin.${BASE_DOMAIN}与${BASE_DOMAIN}下的前端页面使用 Browser History 路由;如果你访问诸如/conversations或/agents这类深链接并刷新页面,容器内 Nginx 必须保留try_files $uri $uri/ /index.html;的回退规则。若需要兼容旧书签,可额外保留/studio/*到根路径的重定向。- 若想临时禁用内置 labels,可设置
TRAEFIK_ENABLE=false,改回在 Dokploy UI 手动配置。 - 如需让浏览器可直接下载 MinIO 预签名链接,仍需单独配置
MINIO_PUBLIC_ENDPOINT(可选独立 MinIO 域名)。 - 修改
BASE_DOMAIN、APP_INSTANCE或 labels 后,需要重新部署 compose 才会生效。
Admin 前端路径约束
为了避免重复发生根路径迁移,当前部署约定固定如下:
- Studio 是默认且唯一的根路径 Admin 前端,公开入口为
https://admin.${BASE_DOMAIN}/。 - 后续若新增其它 Admin 前端,必须挂到新的子路径,例如
https://admin.${BASE_DOMAIN}/ops/。 - 不要让多个 Admin 前端轮流占用根路径
/;这会同时触发 Vite base、Nginx 路由、深链接回退、E2E 脚本、本地运行时 URL 和文档的联动调整。
前端深链接刷新自检
每次发布 admin-frontend 或 demo-frontend 镜像后,至少做一次深链接刷新验证:
- 打开
https://admin.${BASE_DOMAIN}/conversations。 - 在浏览器中直接刷新当前页面。
- 再打开
https://admin.${BASE_DOMAIN}/agents并重复刷新。 - 确认两个页面都返回
200并正常渲染,而不是 Nginx404。 - 如需额外验证 demo 前端,也可直接打开一个非根路径前端路由并重复刷新测试。
使用自有 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/outlook.${BASE_DOMAIN}->outlook-addin:80,Path/outlook.${BASE_DOMAIN}->demo-backend:3000,Path/api(开启 Strip Prefix)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(可选) - S3 兼容存储:
BACKUP_S3_BUCKET+BACKUP_S3_PREFIX(可选)
建议将上述非敏感默认值写入 config.toml 的 [backup];仅将敏感项(如 BACKUP_ENCRYPTION_PASSPHRASE、BACKUP_S3_ACCESS_KEY_ID、BACKUP_S3_SECRET_ACCESS_KEY)放在环境变量或密钥管理系统中。
远程复制触发条件(必须同时满足):
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
S3 兼容远程存储(Backblaze B2 / AWS S3 / R2)
启用 S3 上传后,备份归档会在本地存档的同时自动上传到 S3 兼容存储,并按 retention_days 清理过期远程归档。
在 config.toml 中配置:
[backup]
s3_enabled = true
s3_endpoint = "https://s3.us-west-004.backblazeb2.com" # Backblaze B2 示例
s3_bucket = "my-aibot-backups"
s3_prefix = "backups"
敏感凭据通过环境变量配置(Dokploy 面板或 .env):
BACKUP_S3_ACCESS_KEY_ID=your_b2_key_id
BACKUP_S3_SECRET_ACCESS_KEY=your_b2_application_key
从 S3 恢复:
# 列出 S3 上的可用备份(输出的文件名为相对于 BACKUP_S3_PREFIX 的名称)
scripts/backup/restore_all.sh --s3-list
# 从 S3 下载并恢复(--s3-key 需要传完整 key,即 <BACKUP_S3_PREFIX>/<filename>)
scripts/backup/restore_all.sh --s3-key <BACKUP_S3_PREFIX>/aibot-backup-20260323T180000Z.tar.gz.enc
自动备份(backup-cron 容器)
docker-compose.deploy.yml 包含 backup-cron 服务,是预构建镜像,默认使用 tiered 模式自动执行分层备份:
- PostgreSQL:
pg_dump逻辑导出 → 加密归档 → 上传 B2(MB~GB 级) - MinIO:
mc mirror增量同步到 B2(S3→S3,只传新增/变更对象,不经本地打包) - Qdrant:跳过(可从 PG 元数据 + 文档重建向量)
该容器通过 Docker label(com.docker.compose.project / com.docker.compose.service)查找 postgres/minio 容器,无需挂载 compose 文件(composeless 模式)。
环境变量配置:
| 变量 | 默认值 | 说明 |
|---|---|---|
BACKUP_CRON_SCHEDULE |
0 18 * * * |
cron 表达式(默认 UTC+8 凌晨 2 点) |
BACKUP_MODE |
tiered |
备份模式(tiered 分层 / hot 全量热备 / cold 冷备 / all 全部) |
BACKUP_ENCRYPTION_PASSPHRASE |
— | 归档加密口令(必填) |
BACKUP_S3_ENABLED |
false |
是否启用 S3(tiered 模式下必须启用) |
BACKUP_S3_ENDPOINT |
— | S3 端点 |
BACKUP_S3_BUCKET |
— | S3 桶名 |
BACKUP_S3_ACCESS_KEY_ID |
— | S3 密钥 ID |
BACKUP_S3_SECRET_ACCESS_KEY |
— | S3 密钥 |
BACKUP_S3_MINIO_PREFIX |
minio-mirror |
MinIO 镜像在 S3 中的前缀 |
手动触发备份(在服务器上执行):
# 在 backup-cron 容器内手动执行分层备份
docker exec -it $(docker ps -q --filter "label=com.docker.compose.service=backup-cron") \
/opt/backup/backup_all.sh --mode tiered
# 其他模式
docker exec -it $(docker ps -q --filter "label=com.docker.compose.service=backup-cron") \
/opt/backup/backup_all.sh --mode hot # 全量热备(PG+MinIO+Qdrant 打包)
# 跳过加密(仅调试用)
docker exec -it $(docker ps -q --filter "label=com.docker.compose.service=backup-cron") \
/opt/backup/backup_all.sh --mode tiered --skip-encryption
# 跳过 S3 上传(仅本地归档)
docker exec -it $(docker ps -q --filter "label=com.docker.compose.service=backup-cron") \
/opt/backup/backup_all.sh --mode tiered --skip-s3
查看备份日志:
docker logs backup-cron
从分层备份恢复
# 1. 从 B2 下载 PG 归档并恢复数据库 + 从 B2 同步文档回 MinIO
scripts/backup/restore_all.sh --s3-key <BACKUP_S3_PREFIX>/aibot-backup-xxx.tar.gz.enc --sync-minio-from-s3
# 2. 启动 ai-service,让 startup reconciliation 先做启动期向量核验
# 3. 在受影响的 knowledge source 上运行一次手动 inspect,再决定是否需要 rebuild
调度建议
- 自动分层备份:由
backup-cron容器按BACKUP_CRON_SCHEDULE执行(默认每天 UTC+8 凌晨 2 点) - 手动全量备份:需要时通过
just backup-all或just backup-hot执行 - 手动冷备:通过
just backup-cold执行(会短暂停止服务) - 每月至少一次恢复演练,并记录
restore-report-*.json
恢复输出
恢复脚本会输出机器可读报告到 BACKUP_RESTORE_REPORT_DIR,包含:
- 恢复耗时(
duration_seconds) - 是否满足
RTO <= 30 分钟 - PostgreSQL / MinIO / Qdrant 的基础校验结果
- “数据库已恢复但向量索引明显缺失” 的 warning 字段(
warnings.vector_index_obviously_missing) - 健康检查状态
如果 warnings.vector_index_obviously_missing = true,表示 restore 仅恢复了业务数据库元数据,而 Qdrant 看起来仍接近空库。此时不要把文档继续视为健康索引,推荐顺序是:
- 启动
ai-service,让 startup reconciliation 先做一次启动期粗核验。它会按当前配置的 Qdrant read collections 一并核验,而不是只看主写集合,但它不会直接重建缺失向量。 - 对受影响的 knowledge source 运行手动 inspect:
POST /knowledge-sources/{source_id}/inspect-runs+inspection_mode=vector_health,用于持久化一次 source-scoped live 核验 run。- 如果你在恢复后还想评估新的切分方案,再运行
inspection_mode=chunking_preview,它只做预演,不会写 chunks/vectors。 - 查看 inspect 结果和知识源文档的
vector_status,确认哪些文档仍是reindex_required或verification_failed。 - 仅对这些文档执行
scripts/rebuild_from_snapshot.py做真正的向量重建。
自定义建议
- 使用外部数据库时,请将
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 自动触发,但仅构建并部署受影响的 app 服务 - CD Production 发布:语义化 tag push 自动触发(匹配
v*,例如v1.2.3) - CD 回滚:手动触发
workflow_dispatch并填写rollback_tag(即历史 Git SHA 镜像标签)
CI / CD 默认按路径收紧触发范围:
ai_service/、ai_agent/、main.py、config.toml、alembic.ini、scripts/、Python 依赖变更会触发ai-servicedemo-backend/仅触发demo-backenddemo-frontend/仅触发demo-frontendadmin-frontend/仅触发admin-frontendoutlook-addin/仅触发outlook-addindocs/、mkdocs.yml,以及会影响 API 文档的 Python 源码变更会触发docs-site.github/workflows/、docker-compose*.yml、scripts/deploy/、.env.app.deploy.example仍然视为全量 app 发布变更
其中:
- CI 只运行受影响的检查 job
main上仅文档相关改动时,CD 只构建并部署docs-site- tag 发布与手动 app 发布仍保持全量 app 构建,避免 release 漏项
镜像仓库与标签策略
- 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 | Repository secret | 登录 registry.zata.cafe | |
| REGISTRY_PASSWORD | Repository secret | 登录 registry.zata.cafe | |
| DOKPLOY_API_KEY | staging Environment secret | staging 所在 Dokploy 的 API Key(用于更新 compose 环境变量) | |
| DOKPLOY_STAGING_APP_DEPLOY_HOOK | staging Environment secret | staging app 发布 webhook | |
| DOKPLOY_STAGING_INFRA_DEPLOY_HOOK | staging Environment secret | staging infra 发布 webhook | |
| DOKPLOY_API_KEY | production Environment secret | production 所在 Dokploy 的 API Key(用于更新 compose 环境变量) | |
| DOKPLOY_PROD_APP_DEPLOY_HOOK | production Environment secret | 生产环境 app 发布 webhook(手动审批后触发) | |
| DOKPLOY_PROD_INFRA_DEPLOY_HOOK | production Environment secret | 生产环境 infra 发布 webhook(手动审批后触发) | |
| PROD_HEALTHCHECK_URL | production Environment secret | 生产发布后健康检查地址 | https://admin.aibot.zata.cafe/api/healthz 或 https://api.aibot.zata.cafe/healthz |
必需 GitHub Variables
| Variable | 作用域 | 用途 | 示例 |
|---|---|---|
| DOKPLOY_STAGING_APP_COMPOSE_ID | staging Environment variable | staging app compose ID(用于查询和更新 staging app compose) | |
| DOKPLOY_STAGING_INFRA_COMPOSE_ID | staging Environment variable | staging infra compose ID(用于查询和更新 staging infra compose) | |
| DOKPLOY_API_BASE_URL | staging Environment variable | staging 所在 Dokploy API 基础地址(例如 https://dokploy-staging.example.com/api) | |
| DOKPLOY_PROD_APP_COMPOSE_ID | production Environment variable | production app compose ID(用于查询和更新 production app compose) | |
| DOKPLOY_PROD_INFRA_COMPOSE_ID | production Environment variable | production infra compose ID(用于查询和更新 production infra compose) | |
| DOKPLOY_API_BASE_URL | production Environment variable | production 所在 Dokploy API 基础地址(例如 https://dokploy-prod.example.com/api) | |
说明:staging 与 production 部署在不同服务器,DOKPLOY_API_KEY 和 DOKPLOY_API_BASE_URL 须在各自 Environment 中分别配置。compose ID 为非敏感标识符,配置为 Variable 便于在 Actions 日志中可见、在 GitHub UI 中可读。
环境门禁(Manual Approval)
- 在 GitHub 仓库创建 Environments:
stagingproduction- 为
production配置 Required reviewers,即可在 CD 中实现人工审批后发布。
Staging 发布流程(main push)
- 根据本次
mainpush 的变更范围,构建并推送受影响服务的不可变标签${GIT_SHA}到registry.zata.cafe - 调用
stagingEnvironment 中配置的 Dokploy API,仅将受影响服务对应的*_IMAGE更新为${GIT_SHA} - 触发
DOKPLOY_STAGING_APP_DEPLOY_HOOK
说明:
- 若本次只改动文档或 MkDocs 配置,staging 仅更新
DOCS_SITE_IMAGE - 若
docs-site:${GIT_SHA}不存在,发布流程会保持当前DOCS_SITE_IMAGE,不阻塞其他核心服务发布
Production 发布流程(tag push)
- 创建并推送符合
v*的 Git tag(例如v1.2.3) - CD 对 app 服务执行全量构建,并推送不可变标签
${GIT_SHA}到registry.zata.cafe - 进入
production环境审批 - 审批通过后,调用
productionEnvironment 中配置的 Dokploy API,将 production compose 的*_IMAGE更新为${GIT_SHA} - 触发
DOKPLOY_PROD_APP_DEPLOY_HOOK - 轮询
PROD_HEALTHCHECK_URL直到通过或超时失败 - 同一条 tag 发布额外生成 Outlook Add-in Release 附件:
outlook-addin-installer-production.exeoutlook-addin-installer-production.sha256outlook-addin-manifest-production.xml
回滚流程
方案 A:GitHub Actions 手动回滚(推荐)
- 打开
CDworkflow 的Run workflow - 填写
rollback_tag(目标历史 SHA) - 流程会使用
productionEnvironment 中配置的 Dokploy API,将 production compose 的*_IMAGE更新为${rollback_tag} - 自动触发生产 deploy hook 并执行健康检查
说明:若该历史 tag 尚未包含 docs-site 镜像,回滚流程会保留现有 DOCS_SITE_IMAGE,不会阻塞其他核心服务回滚。
注意:当前回滚流程只会切换镜像标签,不会自动把 PostgreSQL schema 或
alembic_version 一并回退。若数据库已经被新镜像迁移到更高 revision,而旧镜像
在启动时仍使用 DB_MIGRATION_MODE=auto,旧镜像可能在启动阶段报错:
Can't locate revision identified by '<new_revision>'。
方案 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(仅脚本模式使用)。
同样需要注意:compose-rollback.sh 当前只负责切换 *_IMAGE,不会自动执行
alembic downgrade 或 alembic stamp。
镜像回滚但数据库版本已前进时
出现以下现象时,优先按应急流程处理,而不是反复重启旧镜像:
- 新版本已经成功执行过
alembic upgrade head - 回滚到旧镜像后,
ai-service启动日志出现Can't locate revision identified by '<new_revision>' - 或
subprocess.CalledProcessError明确来自uv run alembic ... upgrade head
建议处理顺序:
- 先把回滚目标环境的
DB_MIGRATION_MODE改成manual,重新部署旧镜像,优先恢复服务。 - 在可以访问目标数据库的环境中执行
uv run alembic current,确认当前数据库记录的 revision。 - 如果旧镜像可以兼容当前 schema,但不兼容新的 revision 标记,执行
uv run alembic stamp <target_revision>校准alembic_version。 - 只有在确认目标 migration 的
downgrade()安全、且允许回退 DDL 时,才执行uv run alembic downgrade <target_revision>。
stamp 适合生产应急,因为它不会删除列或表;downgrade 会执行真实 schema 回退,可能带来数据丢失风险。更完整的操作边界见 数据库迁移与版本管理。