# 自动构建与自动重启流程 本文档说明当前项目通过 Git remote + `post-receive` hook 实现自动构建、自动重启的流程。当前服务器端运行方式已经容器化,核心文件为 [.githooks/post-receive](../.githooks/post-receive)、[Dockerfile](../Dockerfile) 和 [docker-compose.yml](../docker-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 配置示例: ```bash test root@43.106.13.130:/root/cozsweet-repos/test production root@43.106.13.130:/root/cozsweet-repos/main ``` 常用发布入口: ```bash # 发布测试环境 ./scripts/release/pre_release_web.sh # 发布生产环境 ./scripts/release/release_web.sh ``` 也可以只执行推送脚本: ```bash # 推送 test remote 的 test 分支 ./scripts/deploy/deploy_web_test.sh # 推送 production remote 的 main 分支 ./scripts/deploy/deploy_web.sh ``` ## 本地发布流程 测试环境发布流程: 1. 进入 test worktree:`cozsweet-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 worktree:`cozsweet-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-` | | `test` | `env-example/.env.local.example` → `.env.local` | `9135` | `cozsweet-web-test` | `cozsweet-web:test-` | | `dev` | `env-example/.env.development.example` → `.env.local` | `9135` | `cozsweet-web-dev` | `cozsweet-web:dev-` | | 其他分支 | 使用默认 `.env.local` | `9135` | `cozsweet-web-` | `cozsweet-web:-` | 本地生产发布脚本和服务器端 `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/static` 和 `public`。 6. 容器通过 `node server.js` 启动,不再依赖完整 `node_modules` 或 `next start`。 注意:`NEXT_PUBLIC_*` 变量会在 `next build` 时固化到前端 bundle,因此测试环境和生产环境必须分别构建镜像,不能共用同一个镜像。 ## 自动重启机制 重启由 Docker Compose 实现: 1. `post-receive` 先执行 `docker compose build web`。 2. 构建失败时直接中止,旧容器继续运行。 3. 构建成功后执行: ```bash docker compose up -d --remove-orphans web ``` 4. Compose 会使用新镜像重建服务容器。 5. 容器配置了 `restart: unless-stopped`,容器异常退出后由 Docker 自动拉起。 6. 容器内固定监听 `3000`,宿主机端口按分支映射为 `9135` 或 `9185`。 首次迁移时,如果宿主机端口仍被旧的裸机 `next start` 进程占用,hook 会在构建成功后、启动容器前清理旧进程。后续如果同名容器已经存在,则不再手动清理端口,由 Docker Compose 管理容器替换。 ## 日志与排查 服务器端日志文件: ```bash logs/post-receive.log ``` 当前日志策略是每次 `post-receive` 执行时覆盖旧日志,而不是追加。 常见排查命令: ```bash # 查看最近一次部署日志 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 ``` 镜像构建失败时,日志中会出现: ```text docker build EXIT_CODE=<非 0> ABORTED ... (docker build failed, old container kept running) ``` 这种情况下不会停止旧容器,也不会启动新容器。 容器启动失败时,日志中会出现: ```text docker compose up EXIT_CODE=<非 0> ABORTED ... (docker compose up failed) ``` 这种情况下需要人工查看 `docker compose ps`、`docker logs ` 和宿主机端口占用。 ## 关键约束 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. 增加旧镜像清理策略,避免服务器磁盘被历史镜像占满。