Files
cozsweet-frontend-nextjs/docs/auto-build-restart.md
T

11 KiB
Raw Blame History

自动构建与自动重启流程(旧 Git Hook 方案)

本文档记录旧的 Git remote + post-receive hook 部署流程,仅作为回滚参考。当前推荐方案已迁移为 Gitea Actions 构建镜像并通过 SSH 部署,见 Gitea Actions SSH 部署流程

旧方案核心文件为 .githooks/post-receiveDockerfiledocker-compose.yml

服务器默认仍会本地构建 Docker 镜像;如果服务器工作树中存在 .deploy.env 且配置 COZSWEET_DEPLOY_IMAGE_SOURCE=pull,则会改为从镜像仓库拉取由 Gitea Actions 构建好的镜像。

旧部署入口

旧方案涉及两个服务器 remote

环境 Git remote 推送分支 宿主机端口 容器端口 部署脚本
测试环境 test test 9135 3000 scripts/deploy/deploy_web_test.sh
生产环境 production main 9185 3000 scripts/deploy/deploy_web.sh

当前 remote 配置示例:

test       root@43.106.13.130:/root/cozsweet-repos/test
production root@43.106.13.130:/root/cozsweet-repos/main

当前日常发布入口已经改为推送 Gitea 分支并触发 Actions,详见 Gitea Actions SSH 部署流程scripts/deploy/* 已改为推送 gitea remote,不再推送服务器 remote。旧方案如果需要手动触发,需要直接操作对应服务器仓库。

本地发布流程

旧测试环境发布流程(脚本迁移前):

  1. 进入 test worktreecozsweet-frontend-nextjs.worktrees/cozsweet-nextjs-test
  2. 确认当前分支。
  3. 执行 git rebase dev,把 dev 的最新代码变基到 test 分支。
  4. 复制测试环境图标到 public/
  5. 复制 env-example/.env.local.example.env.local
  6. 执行旧版 scripts/deploy/deploy_web_test.sh
  7. 旧版 deploy_web_test.sh 推送 test remote 的 test 分支。
  8. 推送成功后尝试清除 Cloudflare CDN 缓存。

旧生产环境发布流程(脚本迁移前):

  1. 进入 main worktreecozsweet-frontend-nextjs.worktrees/cozsweet-nextjs-main
  2. 确认当前分支。
  3. 执行 git rebase dev,把 dev 的最新代码变基到 main 分支。
  4. 复制生产环境图标到 public/
  5. 复制 env-example/.env.production.example.env.production
  6. 执行旧版 scripts/deploy/deploy_web.sh
  7. 旧版 deploy_web.sh 强制推送 production remote 的 main 分支。
  8. 推送成功后尝试清除 Cloudflare CDN 缓存。

服务器端 post-receive 流程

服务器收到 push 后,post-receive hook 会自动执行以下步骤:

  1. 解析当前仓库目录,进入服务端工作树。
  2. 初始化日志文件:logs/post-receive.log
  3. 覆盖写入本次执行元信息,包括时间、分支、commit、cwd。
  4. 执行 git reset --hard HEAD,让服务端工作树同步到刚推送的最新 commit。
  5. 根据当前分支复制环境变量文件。
  6. 根据当前分支选择 Next.js 启动端口。
  7. 根据分支导出 Docker Compose 所需变量,包括镜像名、容器名、env 文件和端口。
  8. 如果使用默认 build 模式,执行 docker compose build web 构建环境专属镜像。
  9. 如果使用 pull 模式,执行 docker compose pull web 拉取 CI 已发布镜像。
  10. 如果镜像构建或拉取失败,写入失败日志并中止,旧容器继续运行。
  11. 如果镜像准备成功,执行 docker compose up -d --remove-orphans web 重建并启动容器。

分支、环境变量、端口映射

post-receive 当前按服务端工作树所在分支判断环境:

服务端分支 环境变量复制 宿主机端口 容器名 镜像标签
main env-example/.env.production.example.env.production 9185 cozsweet-web-prod cozsweet-web:prod-<commit>
test env-example/.env.local.example.env.local 9135 cozsweet-web-test cozsweet-web:test-<commit>
dev env-example/.env.development.example.env.local 9135 cozsweet-web-dev cozsweet-web:dev-<commit>
其他分支 使用默认 .env.local 9135 cozsweet-web-<branch> cozsweet-web:<branch>-<commit>

如果 .deploy.env 配置为 pull 模式,镜像标签会从本地标签切换为远程仓库标签:

COZSWEET_DEPLOY_IMAGE_SOURCE=pull
COZSWEET_REGISTRY_IMAGE=gitea.banlv-ai.com/admin/cozsweet-web

默认远程镜像 tag 为 <deploy_env>-<short_sha>

gitea.banlv-ai.com/admin/cozsweet-web:test-<commit>
gitea.banlv-ai.com/admin/cozsweet-web:prod-<commit>

也可以在服务器本地 .deploy.env 中覆盖为 latest tag

COZSWEET_REMOTE_IMAGE_TAG=prod-latest

推荐生产发布使用默认精确 tag,避免 latest 因缓存或并发发布导致版本不可追踪。

本地生产发布脚本和服务器端 post-receive 已统一使用 .env.production。Next.js 在生产构建时会读取 .env.production;测试和开发发布仍使用 .env.local

本地部署脚本清除 Cloudflare CDN 缓存时,也会按当前分支读取 env:main 优先读取 .env.production,其他分支优先读取 .env.local。如果优先文件不存在或缺少 CF_ZONE_ID / CF_API_TOKEN,会尝试读取另一个文件作为兜底。

生产容器化启用与验证

生产环境容器化通过 main 分支触发。发布入口为:

./scripts/release/release_web.sh

发布完成后,可在生产服务器工作树中验证:

cd /root/cozsweet-repos/main

# 确认生产工作树已更新到 main 最新 commit
git branch --show-current
git rev-parse --short HEAD

# 查看生产容器
docker ps --filter 'name=cozsweet-web-prod'
docker inspect cozsweet-web-prod --format '{{.State.Health.Status}}'

# 验证生产宿主机端口和 PWA service worker
curl -I http://127.0.0.1:9185/
curl -I http://127.0.0.1:9185/serwist/sw.js

如果本次发布同时更新了 .githooks/post-receive 本身,而服务器这次 push 仍由旧 hook 执行,可在生产工作树中手动触发一次新 hook:

cd /root/cozsweet-repos/main
./.githooks/post-receive

后续生产发布会自动使用新的容器化 hook。

镜像来源

默认:服务器本机构建

未配置 .deploy.env 时,post-receive 使用原有模式:

docker compose build web
docker compose up -d --remove-orphans web

推荐:CI 构建,服务器拉取

Gitea Actions 通过 .gitea/workflows/docker-image.yml 构建并推送镜像。服务器只负责拉取镜像:

docker compose pull web
docker compose up -d --remove-orphans --no-build web

服务器需要提前执行一次 registry 登录:

docker login REGISTRY_HOST

服务器本地 .deploy.env 不提交到仓库,示例见 env-example/.deploy.env.example

容器构建机制

Docker 构建使用 Next.js standalone 输出:

  1. next.config.ts 设置 output: "standalone"
  2. Dockerfile 使用 multi-stage build。
  3. pnpm 依赖安装使用 BuildKit cache mount 复用 /pnpm/store
  4. Next.js 构建使用 BuildKit cache mount 复用 .next/cache
  5. build 阶段通过 Docker BuildKit secret 挂载 .env.local.env.production
  6. build 完成后删除临时 env 文件,避免 env 文件被复制到最终镜像。
  7. runtime 阶段复制 .next/standalone.next/staticpublic 和生产依赖 node_modules
  8. 生产依赖用于支持 Serwist 在运行时生成 /serwist/sw.js,避免手动修复 pnpm symlink。
  9. 容器通过 node server.js 启动,不再使用 next start

注意:NEXT_PUBLIC_* 变量会在 next build 时固化到前端 bundle,因此测试环境和生产环境必须分别构建镜像,不能共用同一个镜像。

自动重启机制

重启由 Docker Compose 实现:

  1. post-receive 先准备镜像:默认执行 docker compose build webpull 模式执行 docker compose pull web
  2. 镜像准备失败时直接中止,旧容器继续运行。
  3. 镜像准备成功后执行:
docker compose up -d --remove-orphans web

pull 模式下会额外加上 --no-build,确保服务器不会意外本地构建:

docker compose up -d --remove-orphans --no-build web
  1. Compose 会使用新镜像重建服务容器。
  2. 容器配置了 restart: unless-stopped,容器异常退出后由 Docker 自动拉起。
  3. 容器内固定监听 3000,宿主机端口按分支映射为 91359185
  4. hook 不再主动清理宿主机端口进程,端口和容器生命周期交给 Docker Compose 管理。

日志与排查

服务器端日志文件:

logs/post-receive.log

当前日志策略是每次 post-receive 执行时覆盖旧日志,而不是追加。

常见排查命令:

# 查看最近一次部署日志
tail -n 200 logs/post-receive.log

# 查看容器状态
docker ps --filter 'name=cozsweet-web'

# 查看容器日志
docker logs --tail=200 cozsweet-web-test
docker logs --tail=200 cozsweet-web-prod

# 查看宿主机端口占用
ss -tulnp | grep ':9135'
ss -tulnp | grep ':9185'

# 查看当前分支和 commit
git branch --show-current
git rev-parse --short HEAD

镜像构建或拉取失败时,日志中会出现:

docker build EXIT_CODE=<非 0>
ABORTED ... (docker build failed, old container kept running)

或:

docker pull EXIT_CODE=<非 0>
ABORTED ... (docker pull failed, old container kept running)

这种情况下不会停止旧容器,也不会启动新容器。

容器启动失败时,日志中会出现:

docker compose up EXIT_CODE=<非 0>
ABORTED ... (docker compose up failed)

这种情况下需要人工查看 docker compose psdocker logs <container> 和宿主机端口占用。

关键约束

  1. scripts/deploy/* 只负责推送代码,不负责本地构建和本地启动。
  2. 自动构建和自动重启只发生在服务器端 post-receive hook。
  3. main 分支对应生产环境,test 分支对应测试环境。
  4. 生产推送当前使用 git push --force production main
  5. 构建失败时不会重启容器,旧容器继续运行。
  6. 服务启动由 Docker Compose 管理,不再使用 nohup pnpm run start
  7. 服务器需要安装 Docker,并支持 docker compose 或旧版 docker-compose 命令。
  8. 测试环境和生产环境都通过同一套 post-receive 容器化流程部署,差异仅由分支、环境变量文件和端口决定。

后续优化建议

  1. 保留最近 N 次部署日志,而不是只保留最后一次日志,方便排查历史问题。
  2. 将镜像推送到私有镜像仓库,服务器只负责拉取和启动镜像,减少服务器构建压力。
  3. 增加部署后健康检查,确认新容器真正可访问后再判定部署成功。
  4. 增加旧镜像清理策略,避免服务器磁盘被历史镜像占满。