从 GitHub Actions 到本地兜底发布:AI Mind 容器化上线的一次真实收口复盘 本文为作者原创首发于掘金现同步发布到 CSDN。内容整理自AI Mind项目的真实开发过程。GitHubhttps://github.com/HWYD/ai-mind对应代码版本v0.2.7线上体验https://ai.hwyblog.cloud/instant-mindAI Mind 是一个基于 Next.js 持续迭代的 AI Chat 项目项目从本地大模型聊天起步逐步扩展流式协议、工具调用、MCP、Skill Runtime 和 Agent 等能力。如果这篇文章或 AI Mind 项目对你有所帮助也欢迎到 GitHub 给项目点个 Star⭐这会是对我继续整理后续版本复盘很大的鼓励。AI Mind 做到现在已经不只是一个本地能跑的 AI Chat Demo 了。它里面有 Next.js webapp有独立的project-assistant-service有模型 Provider Runtime有 MCP Host有 Skill Runtime也有 Tasklist Agent Graph 这条受控 Agent 链路。能力越往后做项目越不像“一个前端页面”而更像一个需要认真上线和运维的小型 AI 应用。一开始我对部署链路的想法很直接代码打 tagGitHub Actions 自动构建镜像推到镜像仓库再让服务器拉取镜像完成部署。听起来很标准也很顺。但真实跑下来以后我发现上线链路不能只看设计图。代码仓库、镜像仓库、CI runner、云服务器、生产 env每一段链路都可能成为不稳定点。最后 AI Mind 的部署链路并没有走向“全自动化到底”而是逐步收口成了一个更实际的结构GitHub Actions 保留 CI 和默认 Release 能力 腾讯云 TCR 作为生产镜像仓库 本地脚本负责服务器部署 必要时本地也可以兜底构建并推送生产镜像这篇文章复盘的重点不是 Docker 命令怎么写而是一个 AI 应用在真实上线过程中部署链路是怎么从理想方案一步步收口成可用方案的。最初的计划GitHub Actions GHCR 服务器拉镜像最开始我选择的是 GitHub Container Registry也就是 GHCR。这个选择很自然。代码在 GitHubGitHub Actions 也在 GitHub镜像顺手推到 GHCR。整个链路看起来很短push v*.*.* tag - GitHub Actions 构建 Docker 镜像 - 推送到 GHCR - SSH 登录腾讯云服务器 - 服务器 docker login ghcr.io - docker compose pull/up当时 webapp 和project-assistant-service会分别构建成两个镜像ghcr.io/owner/ai-mind-webapp:sha-git-sha ghcr.io/owner/ai-mind-project-assistant-service:sha-git-sha服务器侧用 Docker Compose 管理两个服务Nginx 做 HTTPS 反向代理对外暴露https://ai.hwyblog.cloud这个方案设计上没有太大问题。GitHub Actions 做构建GHCR 做镜像仓库服务器拉镜像运行逻辑很清楚。但上线不是只看逻辑还要看真实网络。很快我遇到第一个问题腾讯云服务器访问 GHCR 并不稳定。镜像仓库如果拉取不稳定后面的部署流程设计得再漂亮也没意义。于是第一个调整就出现了镜像仓库从 GHCR 切到腾讯云 TCR。从 GHCR 切到腾讯云 TCR镜像仓库要贴近运行环境切到 TCR 后镜像地址变成ccr.ccs.tencentyun.com/hwyd-ai-mind/ai-mind-webapp:production ccr.ccs.tencentyun.com/hwyd-ai-mind/ai-mind-project-assistant-service:production这一步表面上只是换了一个镜像仓库实际上是部署链路的第一次收口。最初我把镜像仓库放在 GitHub是因为代码和 CI 都在 GitHub。但服务器在腾讯云真实部署时服务器能不能稳定拉镜像更重要。所以这个调整背后的判断是镜像仓库不一定要离代码最近而应该离运行环境更稳定。GHCR 对 GitHub Actions 当然方便但腾讯云服务器从腾讯云 TCR 拉镜像更符合实际部署环境。切到 TCR 后第一版计划变成GitHub Actions - build webapp image - build project-assistant-service image - push Tencent TCR - SSH Tencent CVM - server pull TCR image - docker compose up到这里镜像仓库的问题暂时收口了。但第二个问题很快出现了。第二个问题GitHub Actions SSH 腾讯云服务器不稳定切到 TCR 后我仍然保留了最初的“全自动部署”想法GitHub Actions 推完镜像后通过 SSH 登录腾讯云服务器然后执行docker compose pull/up。结果这一段也不稳定。一开始看到的错误很普通ssh: connect to host *** port ***: Connection timed out这种报错很容易让人先去查服务器sshd 是否启动22 端口是否监听UFW 或 iptables 是否拦截腾讯云安全组是否放行deploy 用户和私钥是否正确。这些我都查了一遍。服务器上的 sshd 正常监听防火墙没有明显拦截安全组也放行了。后来我还额外加了 2222 端口测试结果依然不稳定。真正让我确认问题不在服务器的是抓包结果。服务器能看到 GitHub runner 发来的 SYN服务器也返回了 SYN-ACK但后续连接没有正常完成。换句话说连接甚至还没走到 SSH 认证阶段。所以这不是 deploy 用户、私钥或者 sudo 权限的问题而更像是GitHub hosted runner 到腾讯云中国区服务器之间的 TCP 链路不稳定。这一步让我意识到GitHub Actions 可以作为 CI/CD 的一部分但不一定适合承担所有部署动作。第一次部署职责收口GitHub Actions 不再负责 SSH 部署既然 GitHub Actions SSH 服务器不稳定那就不要让它 SSH。于是部署链路做了第二次收口GitHub Actions - build webapp image - build project-assistant-service image - push Tencent TCR 本地 deploy-production.ps1 - SSH 服务器 - docker compose pull - docker compose up - 验证线上状态也就是把原来的一条全自动链路拆成两段镜像发布GitHub Actions 负责 服务器部署本地脚本负责这样一来GitHub Actions 不再需要知道服务器地址、SSH 私钥、远程部署路径也不再直接连接腾讯云服务器。服务器部署由本地脚本触发deploy-production.ps1这个调整之后职责变清楚了GitHub Actions 构建镜像、推镜像 TCR 镜像仓库 本地 PowerShell 脚本 部署控制台 腾讯云服务器 拉镜像、跑容器 Nginx HTTPS 入口这不是完全自动化但更可控也更符合当前项目阶段。生产 env本地维护服务器读取GitHub 不接触AI 应用上线有一个很现实的问题生产 env 怎么管。AI Mind 里面有模型 API Key、MCP Token、Provider 配置还有 Graph Agent 相关开关。这些东西不能写进 Git也不能打进 Docker 镜像。最终我把生产配置分成几类.release.env webapp.production.env project-assistant-service.production.env本地维护在D:\secrets\ai-mind\production服务器读取/srv/ai-mind/.release.env /srv/ai-mind/env/webapp.production.env /srv/ai-mind/env/project-assistant-service.production.env.release.env主要描述镜像地址和 tagwebapp / PAS 的生产 env 则分别描述各自运行时需要的配置。这里有一个细节很容易踩坑webapp 容器访问 PAS不能写127.0.0.1。因为在容器内部127.0.0.1指的是 webapp 容器自己不是另一个容器。正确方式是通过 Docker Compose 的 service name 访问http://project-assistant-service:8788/mcp这个配置最终放在 webapp 的生产 env 里PROJECT_ASSISTANT_SERVICE_MCP_BASE_URLhttp://project-assistant-service:8788/mcp这类生产配置都由本地脚本同步到服务器不进入 GitHub Actions。这样 GitHub Actions 即使打印日志也不会接触模型 Key 和 MCP Token。运行时边界Nginx、Docker Compose 和 MCP最终线上运行结构很简单用户浏览器 - https://ai.hwyblog.cloud - Nginx 443 - webapp 容器 3000 - project-assistant-service 容器 8788公网只暴露 Nginx。webapp 对外提供页面和 APIPAS 只在 Docker 内部网络里给 webapp 调用。公网访问 MCP 不应该成功例如https://ai.hwyblog.cloud/mcp预期应该返回 404。真正的 MCP 调用发生在容器内部webapp - project-assistant-service:8788/mcp这也是上线时需要刻意检查的一点。AI 应用不是把所有端口都开放就完事而是要明确公网入口和内部服务边界。第三个问题GitHub Actions 推 TCR 镜像也会不稳定把服务器 SSH 部署移出 GitHub Actions 后链路稳定了一些。但后来又遇到第三个问题GitHub Actions 推腾讯云 TCR 镜像也会不稳定。这次不是 SSH而是 Docker image push。现象是webapp job 跑到接近 1 小时失败 project-assistant-service job 有时接近 1 小时才成功看日志时真正慢的并不是next build。Next.js 构建已经完成stream-core 也构建完成失败点出现在exporting to image pushing layers Error: The operation was canceled.也就是说卡住的是 Docker 镜像层推送到 TCR。这和前面的 SSH 问题本质类似不是代码本身失败而是 GitHub hosted runner 到腾讯云服务之间的链路不够稳定。于是我又做了一轮收口webapp 和 PAS 拆成两个并行 jobjob timeout 调大关闭 provenance / sbomrelease 阶段减少额外 cache 上传保留 GitHub Actions 默认 release 能力但不再把它视为唯一发布通道。拆并行后结构变成prepare ├─ build-webapp └─ build-project-assistant-service这样至少不会再把两个镜像的构建时间串行叠加。但这只能缓解问题不能彻底解决 GitHub runner 推 TCR 不稳定的问题。第二条发布通道本地 build push 作为兜底最后我决定新增一条本地兜底发布通道。注意这不是替换 GitHub Actions。我仍然保留GitHub Actions CI GitHub Actions Release只是增加一条备用路径当 GitHub Actions 因网络、超时、TCR push 不稳定失败时 本地可以直接 build push TCR production 镜像最终链路变成默认通道 tag - GitHub Actions - TCR - local deploy 兜底通道 tag - local release script - TCR - local deploy本地兜底脚本计划命名为release-production-local.ps1它做几件事读取本地 TCR secret 检查当前 Git 状态 检查当前 commit 是否有 v*.*.* tag 登录 TCR 构建 webapp 镜像 构建 project-assistant-service 镜像 两个 build 都成功后 push production 可选调用 deploy-production.ps1本地兜底通道的重点不是“绕过 CI”而是给发布链路留一个恢复手段。如果 GitHub Actions 是因为 typecheck、test、next build、Dockerfile 语法失败那就应该先修代码。本地兜底只解决一种情况GitHub Actions 不是代码失败而是 runner / 网络 / TCR push 不稳定。这个边界很重要。tag 纪律旧 tag 不复用部署链路收口后我也顺便把 tag 规则定了下来。规则很简单普通 main 提交只跑 CI不上线 正式上线打新的 v*.*.* tag GitHub Actions 失败后本地兜底使用同一个 tag 旧 tag不复用、不强推、不移动例如v0.2.5的 GitHub Actions 因为 TCR push 失败了本地兜底就应该基于同一个v0.2.5执行而不是重新打一个v0.2.6。只有代码发生新的变更才应该进入下一个版本 tag。这个规则看起来有点麻烦但它能保证后面回看 release、TCR 镜像和线上版本时不会出现“这个 production 镜像到底对应哪次代码”的问题。为什么没有追求“全自动化到底”这次部署链路最后没有变成一个特别重的系统。没有 Kubernetes。没有 ArgoCD。没有蓝绿发布。没有复杂的镜像晋级流程。也没有把服务器部署完全交给 GitHub Actions。这是有意为之。AI Mind 现阶段是一个持续迭代中的 AI Native Runtime Skeleton / MVP不是一个大型商业 Agent 平台。这个阶段最重要的不是搭一套看起来很重的 DevOps 系统而是让上线链路满足几个基本要求能发布 能排查 能恢复 secret 不乱放 失败时有兜底 部署过程能讲清楚对个人项目来说可控比“看起来全自动”更重要。我更愿意接受默认自动化 本地兜底而不是看起来全自动但出问题时只能反复 re-run GitHub Actions后续还可以怎么优化这条部署链路还不是终点。后面还有几个方向可以继续做。第一缩小 webapp 镜像。当前 webapp 镜像推送慢长期要从镜像大小入手。Next.js 可以考虑使用output: standalone让最终镜像只复制运行时需要的 standalone 产物而不是复制完整node_modules。第二继续优化 PAS 镜像。project-assistant-service是独立 Node.js 服务后续可以考虑只复制 PAS 运行需要的 dist 和 production dependencies进一步降低镜像体积。第三v0.3.0 后接 Postgres。后续 AI Mind 做 HITL、AgentRun、Resume、Run History 时生产环境会引入 Postgres。到那时部署链路还需要继续扩展数据库 env、迁移和备份策略。第四考虑 self-hosted runner。如果后续仍然希望 GitHub Actions UI 统一管理发布又想解决 GitHub hosted runner 到 TCR 不稳定的问题可以考虑单独准备一台构建机作为 self-hosted runner。但我不会直接让生产服务器承担构建任务。生产服务器应该优先稳定运行服务而不是承担镜像构建压力。这次部署收口带来的经验这次过程下来我最大的感受是上线链路不能只看设计图还要看真实网络、真实权限、真实失败场景。几个经验比较明确。镜像仓库不一定要离代码最近而应该离运行环境更稳定。GitHub Actions 很适合 CI但不一定适合承担所有部署动作。国内云服务器和 GitHub hosted runner 之间的网络链路要实际验证不要只凭设计想象。env 和 secret 要和代码、镜像、部署流程分离。Docker Compose 对个人 AI 应用上线仍然足够实用。本地兜底发布不是倒退而是给发布链路增加恢复能力。版本 tag 要保持稳定不要复用、强推、移动已经发布过的 tag。最后 AI Mind 的部署方案并不是最复杂的方案但它是当前阶段更合适的方案GitHub Actions 保留默认 CI / Release 能力 腾讯云 TCR 作为生产镜像仓库 本地脚本提供服务器部署和兜底发布能力 服务器只负责拉取镜像并稳定运行这条链路没有把所有事情都自动化到底但它把几个关键问题都收口了镜像仓库选择、构建发布、生产 env、服务器部署、网络失败、兜底上线。对一个持续迭代中的 AI 应用来说这比追求“完全自动化”更重要。结尾项目地址 GitHubhttps://github.com/HWYD/ai-mind 线上体验https://ai.hwyblog.cloud/instant-mind如果这篇文章或者 AI Mind 项目对你有所帮助也欢迎给项目点个 Star⭐。你的支持会是我持续更新这个系列、继续整理项目实现过程和设计复盘的很大动力。