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

7.8 KiB
Raw Blame History

自动构建与自动重启流程

本文档说明当前项目通过 Git remote + post-receive hook 实现自动构建、自动重启的流程。当前服务器端运行方式已经容器化,核心文件为 .githooks/post-receiveDockerfiledocker-compose.yml

部署入口

当前涉及两个服务器 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

常用发布入口:

# 发布测试环境
./scripts/release/pre_release_web.sh

# 发布生产环境
./scripts/release/release_web.sh

也可以只执行推送脚本:

# 推送 test remote 的 test 分支
./scripts/deploy/deploy_web_test.sh

# 推送 production remote 的 main 分支
./scripts/deploy/deploy_web.sh

本地发布流程

测试环境发布流程:

  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. 初始化 Node / pnpm 环境。
  2. 解析当前仓库目录,进入服务端工作树。
  3. 初始化日志文件:logs/post-receive.log
  4. 覆盖写入本次执行元信息,包括时间、分支、commit、cwd。
  5. 执行 git reset --hard HEAD,让服务端工作树同步到刚推送的最新 commit。
  6. 根据当前分支复制环境变量文件。
  7. 根据当前分支选择 Next.js 启动端口。
  8. 根据分支导出 Docker Compose 所需变量,包括镜像名、容器名、env 文件和端口。
  9. 执行 docker compose build web 构建环境专属镜像。
  10. 如果镜像构建失败,写入失败日志并中止,旧容器继续运行。
  11. 如果镜像构建成功,执行 docker compose up -d --remove-orphans web 重建并启动容器。
  12. 第一次从裸机 next start 迁移到容器时,如果目标端口上没有同名容器但存在旧进程,会清理旧进程后再启动容器。

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

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>

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

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

容器构建机制

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

  1. next.config.ts 设置 output: "standalone"
  2. Dockerfile 使用 multi-stage build。
  3. build 阶段通过 Docker BuildKit secret 挂载 .env.local.env.production
  4. build 完成后删除临时 env 文件,避免 env 文件被复制到最终镜像。
  5. runtime 阶段只复制 .next/standalone.next/staticpublic
  6. 容器通过 node server.js 启动,不再依赖完整 node_modulesnext start

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

自动重启机制

重启由 Docker Compose 实现:

  1. post-receive 先执行 docker compose build web
  2. 构建失败时直接中止,旧容器继续运行。
  3. 构建成功后执行:
docker compose up -d --remove-orphans web
  1. Compose 会使用新镜像重建服务容器。
  2. 容器配置了 restart: unless-stopped,容器异常退出后由 Docker 自动拉起。
  3. 容器内固定监听 3000,宿主机端口按分支映射为 91359185

首次迁移时,如果宿主机端口仍被旧的裸机 next start 进程占用,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 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 命令。

后续优化建议

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