11 KiB
自动构建与自动重启流程(旧 Git Hook 方案)
本文档记录旧的 Git remote + post-receive hook 部署流程,仅作为回滚参考。当前推荐方案已迁移为 Gitea Actions 构建镜像并通过 SSH 部署,见 Gitea Actions SSH 部署流程。
旧方案核心文件为 .githooks/post-receive、Dockerfile 和 docker-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。旧方案如果需要手动触发,需要直接操作对应服务器仓库。
本地发布流程
旧测试环境发布流程(脚本迁移前):
- 进入 test worktree:
cozsweet-frontend-nextjs.worktrees/cozsweet-nextjs-test - 确认当前分支。
- 执行
git rebase dev,把dev的最新代码变基到test分支。 - 复制测试环境图标到
public/。 - 复制
env-example/.env.local.example到.env.local。 - 执行旧版
scripts/deploy/deploy_web_test.sh。 - 旧版
deploy_web_test.sh推送testremote 的test分支。 - 推送成功后尝试清除 Cloudflare CDN 缓存。
旧生产环境发布流程(脚本迁移前):
- 进入 main worktree:
cozsweet-frontend-nextjs.worktrees/cozsweet-nextjs-main - 确认当前分支。
- 执行
git rebase dev,把dev的最新代码变基到main分支。 - 复制生产环境图标到
public/。 - 复制
env-example/.env.production.example到.env.production。 - 执行旧版
scripts/deploy/deploy_web.sh。 - 旧版
deploy_web.sh强制推送productionremote 的main分支。 - 推送成功后尝试清除 Cloudflare CDN 缓存。
服务器端 post-receive 流程
服务器收到 push 后,post-receive hook 会自动执行以下步骤:
- 解析当前仓库目录,进入服务端工作树。
- 初始化日志文件:
logs/post-receive.log。 - 覆盖写入本次执行元信息,包括时间、分支、commit、cwd。
- 执行
git reset --hard HEAD,让服务端工作树同步到刚推送的最新 commit。 - 根据当前分支复制环境变量文件。
- 根据当前分支选择 Next.js 启动端口。
- 根据分支导出 Docker Compose 所需变量,包括镜像名、容器名、env 文件和端口。
- 如果使用默认
build模式,执行docker compose build web构建环境专属镜像。 - 如果使用
pull模式,执行docker compose pull web拉取 CI 已发布镜像。 - 如果镜像构建或拉取失败,写入失败日志并中止,旧容器继续运行。
- 如果镜像准备成功,执行
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 输出:
next.config.ts设置output: "standalone"。Dockerfile使用 multi-stage build。- pnpm 依赖安装使用 BuildKit cache mount 复用
/pnpm/store。 - Next.js 构建使用 BuildKit cache mount 复用
.next/cache。 - build 阶段通过 Docker BuildKit secret 挂载
.env.local或.env.production。 - build 完成后删除临时 env 文件,避免 env 文件被复制到最终镜像。
- runtime 阶段复制
.next/standalone、.next/static、public和生产依赖node_modules。 - 生产依赖用于支持 Serwist 在运行时生成
/serwist/sw.js,避免手动修复 pnpm symlink。 - 容器通过
node server.js启动,不再使用next start。
注意:NEXT_PUBLIC_* 变量会在 next build 时固化到前端 bundle,因此测试环境和生产环境必须分别构建镜像,不能共用同一个镜像。
自动重启机制
重启由 Docker Compose 实现:
post-receive先准备镜像:默认执行docker compose build web,pull 模式执行docker compose pull web。- 镜像准备失败时直接中止,旧容器继续运行。
- 镜像准备成功后执行:
docker compose up -d --remove-orphans web
pull 模式下会额外加上 --no-build,确保服务器不会意外本地构建:
docker compose up -d --remove-orphans --no-build web
- Compose 会使用新镜像重建服务容器。
- 容器配置了
restart: unless-stopped,容器异常退出后由 Docker 自动拉起。 - 容器内固定监听
3000,宿主机端口按分支映射为9135或9185。 - 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 ps、docker logs <container> 和宿主机端口占用。
关键约束
scripts/deploy/*只负责推送代码,不负责本地构建和本地启动。- 自动构建和自动重启只发生在服务器端
post-receivehook。 main分支对应生产环境,test分支对应测试环境。- 生产推送当前使用
git push --force production main。 - 构建失败时不会重启容器,旧容器继续运行。
- 服务启动由 Docker Compose 管理,不再使用
nohup pnpm run start。 - 服务器需要安装 Docker,并支持
docker compose或旧版docker-compose命令。 - 测试环境和生产环境都通过同一套
post-receive容器化流程部署,差异仅由分支、环境变量文件和端口决定。
后续优化建议
- 保留最近 N 次部署日志,而不是只保留最后一次日志,方便排查历史问题。
- 将镜像推送到私有镜像仓库,服务器只负责拉取和启动镜像,减少服务器构建压力。
- 增加部署后健康检查,确认新容器真正可访问后再判定部署成功。
- 增加旧镜像清理策略,避免服务器磁盘被历史镜像占满。