部署

Dokploy(生产环境)

使用 Dokploy 拉取 GHCR 应用与迁移镜像部署 Makeugcads 到自有 VPS

Dokploy 是一个自托管 PaaS,可在 VPS 上管理 Docker Compose 服务,提供自动 HTTPS、滚动部署和环境变量管理 Web UI。生产环境推荐由 GitHub Actions 构建并推送 GHCR 应用镜像、迁移镜像与 RBAC 初始化镜像,Dokploy 只负责拉取镜像、运行一次性部署任务、注入运行时环境变量并启动容器。

部署路径概览

本仓库的生产路径为:

  1. 推送代码到 maindev
  2. GitHub Actions 构建四个镜像:
    • ghcr.io/chenhaonan-eth/ugcad-web:<tag>
    • ghcr.io/chenhaonan-eth/ugcad-agents:<tag>
    • ghcr.io/chenhaonan-eth/ugcad-migrations:<tag>
    • ghcr.io/chenhaonan-eth/ugcad-rbac-init:<tag>
  3. Dokploy 使用 compose.yml 拉取镜像,连接 Dokploy Databases 中的 PostgreSQL,运行一次性 migrations / rbac-init,并启动 redisagentsweb

PR 只执行构建校验,不推送镜像。生产 Dokploy 不在服务器上构建源码,只拉取 GHCR 已构建镜像。

前置要求

  • VPS:至少 4 核 CPU、8 GB 内存、80 GB 磁盘(推荐 Ubuntu 24.04)
  • 一个域名,DNS A 记录指向服务器 IP
  • LLM API 密钥(OpenAI 或兼容接口)
  • GHCR access,可拉取仓库镜像
  • GitHub repository variables 已配置生产构建期公开变量
  • Dokploy Databases 中已准备 PostgreSQL 实例

Step 1 — 安装 Dokploy

SSH 登录服务器,执行:

curl -sSL https://dokploy.com/install.sh | sh

安装完成后,访问 http://YOUR_SERVER_IP:3000 进入 Dokploy UI,创建管理员账号。

Step 2 — 配置 GitHub Actions 构建变量

web 镜像的 Next.js 客户端 bundle 会在构建期固化公开变量。请在 GitHub 仓库 Settings → Secrets and variables → Actions → Variables 中配置:

NEXT_PUBLIC_APP_URL=https://yourdomain.com
NEXT_PUBLIC_APP_NAME=Makeugcads
NEXT_PUBLIC_THEME=huxibo
NEXT_PUBLIC_APPEARANCE=system
NEXT_PUBLIC_DEFAULT_LOCALE=en

NEXT_PUBLIC_APP_URL 必须在 GitHub repository variables 中于构建期注入,不能只在 Dokploy 运行时设置。更换域名后必须重新构建并发布新的 web 镜像。

上线前必须完成密钥历史审计,确认 Git 历史、Actions 日志和镜像层中没有提交过真实 .env、API Key、数据库密码或 AUTH_SECRET。密钥历史审计是上线前阻塞项,未完成不得部署生产流量。

Step 3 — 准备 Compose 文件

仓库根目录 compose.yml 已切换为生产拉取 GHCR 四镜像:

migrations:
  image: ${GHCR_IMAGE_PREFIX:-ghcr.io/chenhaonan-eth/ugcad}-migrations:${IMAGE_TAG:-latest}

rbac-init:
  image: ${GHCR_IMAGE_PREFIX:-ghcr.io/chenhaonan-eth/ugcad}-rbac-init:${IMAGE_TAG:-latest}

agents:
  image: ${GHCR_IMAGE_PREFIX:-ghcr.io/chenhaonan-eth/ugcad}-agents:${IMAGE_TAG:-latest}

web:
  image: ${GHCR_IMAGE_PREFIX:-ghcr.io/chenhaonan-eth/ugcad}-web:${IMAGE_TAG:-latest}

如需部署 fork 或指定版本,在 Dokploy 环境变量中设置:

GHCR_IMAGE_PREFIX=ghcr.io/chenhaonan-eth/ugcad
IMAGE_TAG=latest

也可以把 IMAGE_TAG 设置为 GitHub Actions 生成的分支 tag 或 sha tag,用于灰度或回滚。同一次部署中,webagentsmigrationsrbac-init 必须使用同一个 tag。

Step 4 — 在 Dokploy 创建项目

  1. 在 Dokploy UI 中点击 Create Project
  2. 输入项目名称(如 makeugcads
  3. 进入项目,点击 Create Service → Docker Compose
  4. 连接 GitHub 仓库读取 compose.yml,或确保服务器部署目录包含完整仓库文件
  5. 确认 Dokploy 部署模式为拉取镜像,不要在服务器上构建源码

生产 PostgreSQL 由 Dokploy Databases 管理,并通过标准 PostgreSQL URL 注入到 Compose 服务。默认生产部署不启用 Compose 内置 postgres;该服务只保留在 legacy-postgres profile 中,用于短期回滚窗口或本地排障。

本次生产路径不引入 Supabase Auth、Supabase Storage、Supabase Realtime、自托管 Supabase 或自托管 Neon。数据库切换只使用 Dokploy Databases 提供的 PostgreSQL 与标准 PostgreSQL URL。

Step 5 — 配置 Dokploy 运行时环境变量

先在 Dokploy Databases 中创建一个 PostgreSQL 服务,再在该服务里准备两个 logical database:一个存业务数据,一个存 LangGraph runtime 状态。应用和数据库位于同一 Dokploy 项目/网络时,优先复制 Dokploy 提供的 internal PostgreSQL URL;保留 Dokploy 提供的 sslmode 等查询参数,并对用户名或密码里的特殊字符做 URL encode。

  • DATABASE_URL 指向业务 logical database,供 migrationsrbac-initweb 使用,承载用户、认证、RBAC、品牌工作区和 UGC 业务表。
  • LANGGRAPH_DATABASE_URL 指向 LangGraph runtime logical database,供 agents 作为 checkpoint/state/store 使用,不与业务表混用。

在 Dokploy 服务设置中添加以下环境变量。真实凭据只能保存到 Dokploy 环境变量/密钥管理中,不要提交到 .env.env.production、Compose override、MDX 文档、GitHub Actions 日志或镜像层:

# ── 镜像选择 ────────────────────────────────────────────
GHCR_IMAGE_PREFIX=ghcr.io/chenhaonan-eth/ugcad
IMAGE_TAG=latest

# ── 基础设施 ──────────────────────────────────────────
DATABASE_URL=postgresql://<user>:<password>@<host>:5432/<business-db>
LANGGRAPH_DATABASE_URL=postgresql://<user>:<password>@<host>:5432/<langgraph-db>
REDIS_PASSWORD=<强随机密码>

# ── Web 运行时 ─────────────────────────────────────────
AUTH_SECRET=<openssl rand -base64 32>
NEXT_PUBLIC_APP_URL=https://yourdomain.com

# ── 内部服务认证 ────────────────────────────────────────
AGENTS_SHARED_SECRET=<随机字符串>
BRAND_WORKSPACE_ACTIVE_BRAND_ID=brand-live

# ── LLM ───────────────────────────────────────────────
OPENAI_API_KEY=sk-your-key
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o

# ── 可选 ──────────────────────────────────────────────
RBAC_ADMIN_EMAIL=
ANTHROPIC_API_KEY=
TAVILY_API_KEY=
MINERU_API_KEY=
TIKHUB_API_TOKEN=
LANGSMITH_API_KEY=
LANGSMITH_TRACING=false
LANGSMITH_TRACING_V2=false
LANGCHAIN_TRACING=false
LANGCHAIN_TRACING_V2=false

禁用 tracing 时,请在 Dokploy 中保持以上四个 tracing 开关为 false 或不设置。残留的 LANGCHAIN_TRACING_V2=true / LANGCHAIN_TRACING=true 仍可能触发 LangSmith 上传 trace,并在 agents 日志中出现 Failed to send multipart request ... 403 Forbidden

Dokploy 中的 NEXT_PUBLIC_APP_URL 仍用于运行时 AUTH_URL 等服务端配置,但客户端可见地址以 GitHub Actions 构建镜像时注入的 repository variable 为准。

Dokploy-managed PostgreSQL 仍是自托管数据库。切换前至少保留一次可恢复备份,并为生产库规划定期备份、恢复演练、磁盘容量监控、连接数/错误日志监控。

Step 6 — 部署

在 Dokploy UI 中点击 Deploy,Dokploy 将:

  1. 从 GHCR 拉取 webagentsmigrationsrbac-init 镜像
  2. 启动 redis
  3. 运行一次性 migrations 服务,把 SQL migration 应用到 DATABASE_URL
  4. migrations 成功后运行一次性 rbac-init 服务,初始化 RBAC 基础数据
  5. 使用 LANGGRAPH_DATABASE_URL 启动 agents,随后启动 web
  6. 注入 Dokploy 环境变量并等待健康检查通过

Step 7 — 应用数据库迁移

数据库迁移通过 compose.yml 中的一次性 migrations 服务执行:

migrations:
  image: ${GHCR_IMAGE_PREFIX:-ghcr.io/chenhaonan-eth/ugcad}-migrations:${IMAGE_TAG:-latest}

该镜像内置 scripts/migrate-postgres.shapps/web/src/config/db/migrations 下的 SQL 文件,并与 webagentsrbac-init 使用同一个 IMAGE_TAG,确保应用代码、RBAC 初始化脚本和数据库结构来自同一 commit。

迁移镜像会按 apps/web/src/config/db/migrations/meta/_journal.json 的顺序执行 SQL,并把成功执行的 tag 写入 public.__ugcad_migrations。首次运行会从空 PostgreSQL 建立基础表;之后重复运行会跳过已执行项。不要手动复制 SQL 到容器,也不要在生产用 db:push 替代 SQL 迁移。

如果你的 Dokploy 版本没有自动运行一次性 Compose 服务,请在 Dokploy UI 中手动运行或重启 migrations 服务,并在日志中确认出现 appliedskipped 后再测试登录注册。

如果你接管的是已有本地/开发库,请先阅读 apps/web/src/config/db/migrations/README.md 的 baseline 说明;不要把本地 journal 不一致误判为生产 Dokploy 初始化失败。

Step 8 — 配置域名和 HTTPS

  1. 在 Dokploy 中进入 web 服务 → Domains
  2. 添加域名(如 app.yourdomain.com
  3. Dokploy 通过 Traefik 自动申请 Let's Encrypt 证书

启用 HTTPS 前,确保 DNS A 记录已指向服务器 IP。

Step 9 — 生产数据库切换 smoke test 清单

不要只用容器状态判断切换成功。按下面清单确认 DATABASE_URLLANGGRAPH_DATABASE_URL、RBAC 初始化、业务读写和回滚窗口都符合预期。

migrations 日志

  • migrations 一次性服务已成功完成。
  • 首次连接新库时,新 migration 日志显示 applied
  • 重复运行时,已记录在 public.__ugcad_migrations 的 migration 显示 skipped
  • 日志中没有 SQL error、认证失败、权限错误或连接错误。
  • 发布记录中写下最后一个 applied / skipped 的 migration tag。

RBAC init

  • rbac-init 一次性服务在 migrations 成功后完成。
  • 未设置 RBAC_ADMIN_EMAIL 的首次部署只初始化 roles / permissions / role_permissions,不要求管理员邮箱已存在。
  • 如果已设置 RBAC_ADMIN_EMAIL,确认该邮箱对应的已注册用户获得 super_admin
  • 管理员账号可以访问受保护的管理功能。
  • 普通用户不能访问管理员功能。
  • 未登录用户访问受保护页面时会进入认证入口或被拒绝。

Web 运行时

# Web 应用
curl https://yourdomain.com/api/health
  • 公开页面可访问。
  • https://yourdomain.com/sign-uphttps://yourdomain.com/sign-in 可访问。
  • /api/health 返回健康状态。
  • 至少执行一条生产已启用的业务写入路径,例如保存品牌工作区设置或创建/更新一个核心业务对象。
  • 刷新页面或重新登录后能读回刚才写入的数据,证明写入落在 DATABASE_URL 指向的业务库。

Agents / LangGraph runtime

# Agents API(仅内网访问)
docker compose exec web wget -qO- http://agents:8000/ok
# → {"ok":true}
  • agents 服务启动成功。
  • web 容器访问 http://agents:8000/ok 返回成功响应。
  • 执行一次会触发 LangGraph runtime checkpoint/state/store 的 agent 流程。
  • 日志或数据库检查确认 runtime 状态写入 LANGGRAPH_DATABASE_URL,没有混入 DATABASE_URL 的业务表。

品牌工作区读写

  • 使用 BRAND_WORKSPACE_ACTIVE_BRAND_ID 打开当前生产品牌工作区。
  • 成功读取品牌资料、素材或核心配置。
  • 完成一次代表性写入,例如保存品牌配置、更新素材元数据或创建/更新当前生产已启用的核心对象。
  • 刷新后确认写入仍存在。
  • 确认写入没有落到旧 Compose PostgreSQL volume。

回滚准备

  • 旧 Compose PostgreSQL volume 或切换前备份仍然保留。
  • 备份位置、生成时间和负责人已记录。
  • 观察窗口结束前没有删除旧 volume。
  • 回滚需要明确切换 DATABASE_URLLANGGRAPH_DATABASE_URL;系统没有双写路径。

更新部署

推荐按 commit tag 更新,同一 tag 必须同时用于 webagentsmigrationsrbac-init

  1. 推送代码到 maindev
  2. 等待 GitHub Actions 成功,确认四个镜像都发布了同一个 tag
  3. 在 Dokploy 中把 IMAGE_TAG 改成新的 tag,推荐使用 sha-...;如果只是改运行时密钥,直接更新 Dokploy 环境变量即可,不需要重新构建镜像
  4. 点击 Redeploy,让 Dokploy 拉取新镜像并重新运行迁移与 RBAC 初始化服务
  5. 查看 migrations 服务日志,确认新的 migration 显示 applied,已执行过的 migration 显示 skipped
  6. 按 Step 9 的 smoke test 清单验证

应用版本回滚的方式相同:把 IMAGE_TAG 改回上一个可用 tag,再 Redeploy,同时保持 DATABASE_URLLANGGRAPH_DATABASE_URL 不变。如果新版本已经执行了不可逆 schema 或数据变更,单纯回滚镜像可能不够,需要恢复切换前的数据库备份。

旧 Compose PostgreSQL volume 建议保留一个短期回滚窗口。如需临时回到内置 PostgreSQL,启用 legacy-postgres Compose profile,并显式设置强 POSTGRES_PASSWORD;未设置时容器会拒绝启动。把业务库和 LangGraph 库都恢复到内置 PostgreSQL 后,先将 DATABASE_URL 指向恢复后的业务库,将 LANGGRAPH_DATABASE_URL 指向恢复后的 LangGraph 库,再重新部署。不要把旧 volume 当作长期生产路径、双写目标或唯一备份策略。

如果你修改的是 NEXT_PUBLIC_APP_URL,先更新 GitHub repository variable,重新构建 web 镜像,再让 Dokploy 拉取新镜像。

常见问题

Dokploy 拉取 GHCR 镜像失败

确认镜像名称与 tag 正确:

GHCR_IMAGE_PREFIX=ghcr.io/chenhaonan-eth/ugcad
IMAGE_TAG=latest

如果仓库镜像为 private,需要在 Dokploy 服务器上配置可读取 GHCR package 的凭据。

修改了域名但页面仍跳转到旧地址

NEXT_PUBLIC_APP_URL 是构建期变量。请先更新 GitHub repository variable,然后重新运行 GitHub Actions 构建并发布新的 web 镜像,再让 Dokploy 拉取新镜像。

agents 无法连接 PostgreSQL 或 Redis

确认 LANGGRAPH_DATABASE_URL 指向 LangGraph logical database,并且 Dokploy 应用能访问该数据库 host。如果 Dokploy 同时显示 internal 和 external PostgreSQL URL,同一 Dokploy 项目/网络内优先使用 internal URL。Redis 仍由 compose.yml 运行,因此所有服务仍需位于同一个 Compose 项目网络。

Web 或 migrations 无法连接 PostgreSQL

确认 DATABASE_URL 指向业务 logical database,而不是 LangGraph database。保存到 Dokploy 环境变量前,保留 Dokploy 提供的 sslmode 等 TLS 参数,并对凭据里的特殊字符做 URL encode。

Web 应用显示 502 Bad Gateway

agents 容器可能还未健康就绪。检查日志:

docker compose logs agents --tail=50

部署后出现 schema 错误

有新的迁移文件未应用,或首次部署后 migrations 服务还没有成功完成。请在 Dokploy 中检查 migrations 服务日志,并重新运行该服务。成功执行时,新 migration 会显示 applied,已记录在 public.__ugcad_migrations 中的 migration 会显示 skipped