# 部署手册(Docker · 蓝绿零停机) ## 0. 一图看懂 ``` 浏览器 ──▶ proxy(nginx:80,常驻,发布时从不重建) ├─ /api ──▶ backend_a 或 backend_b ├─ / ──▶ web_a 或 web_b ├─ 18090(仅容器网络)SSR 取数 → 当前活跃后端 ├─ 127.0.0.1:18091 前端构建期取 SSG └─ 127.0.0.1:18092 管理后台(SSH 隧道) ``` 发布时只重建**空闲槽**,等它健康后改写切换文件并 `nginx -s reload`,在途请求由旧槽跑完。 镜像以 `RELEASE`(`时间戳-后端提交号`)打标签,**不可变**,历史版本不会被覆盖。 部署入口在本仓库(后端仓),脚本会自动把前端仓也拉到位。 --- ## 1. 服务器准备(只做一次) ```bash # Ubuntu 22.04 / 24.04,2 核 4G 起 curl -fsSL https://get.docker.com | sh apt install -y docker-compose-plugin git mkdir -p /opt/fashion && cd /opt/fashion git clone <后端仓库> backend git clone <前端仓库> test # 必须与 backend 同级 cd backend cp .env.example .env && vi .env chmod +x deploy.sh init-db.sh ``` 私有仓库需要配好拉取凭据:SSH deploy key,或 HTTPS token + `git config credential.helper store`。 --- ## 2. 首次部署(只做一次) ```bash ./init-db.sh /path/to/your.sql # 建库(传入与当前代码匹配的 SQL) ./deploy.sh all # 启动全部服务 curl http://127.0.0.1/api/health # {"status":"ok"} ``` `.env` 必填 6 项: | 变量 | 说明 | | --- | --- | | `DB_PASSWORD` | 数据库口令,别沿用开发库的 | | `HASHID_SECRET` | 公开 ID 混淆盐,**上线后不要改**(改了所有旧链接失效) | | `JWT_SECRET` | 登录令牌密钥 | | `INGEST_SECRET` | 爬虫上报密钥,须与爬虫端一致 | | `S4_AK` / `S4_SK` | 对象存储密钥,缺了图片全 404 | --- ## 3. 日常发布 ```bash ./deploy.sh # 拉 main + 只更新后端(默认) ./deploy.sh all # 拉 main + 前后端都更新 ./deploy.sh web # 只更新前端 ./deploy.sh all --no-pull # 不拉代码,用服务器现有代码构建 GIT_BRANCH=dev ./deploy.sh all # 临时指定分支(优先于 .env) ``` **拉代码**(`.env` 的 `GIT_BRANCH`,默认 `main`): - 后端仓 `.` 和前端仓 `$WEB_DIR`(默认 `../test`)都会 `fetch` → 必要时 `checkout` → `pull --ff-only` - **不能快进就报错退出**,不会硬覆盖(有本地改动时会提示 `reset --hard` 命令) - 每次打印实际提交:`后端:main @ a1b2c3d fix: xxx` **发布过程**: | 步 | 动作 | 用户感受 | | --- | --- | --- | | 1 | 生成 `RELEASE`,拉代码 | 无感 | | 2 | `docker compose build` 构建 `fashion-*:$RELEASE` | 无感(旧槽在跑) | | 3 | 重建空闲槽 | 无感 | | 4 | 轮询 health,未通过则**直接退出** | 旧槽继续服务 | | 5 | 改切换文件 → `nginx -t` → `nginx -s reload` | 在途请求不丢 | | 6 | 写 `.deploy/releases.log` | — | --- ## 4. 回滚 ```bash ./deploy.sh --rollback # 回滚 backend:b → a(该槽镜像 fashion-backend:20261018-2210-9f3e1a2) ``` 不动代码、不重建容器,只改 nginx 指向 + reload,秒级生效。 前提是该槽容器还在(正常交替发布时,非活跃槽就是上一版)。 --- ## 5. 版本与镜像管理 ```bash cat .deploy/releases.log # 发布历史 docker inspect -f '{{.Config.Image}}' $(docker compose ps -q backend_a) # 线上版本 ./deploy.sh --prune # 清理旧镜像 KEEP_IMAGES=3 ./deploy.sh --prune # 只留 3 个 ``` 镜像标签形如 `fashion-backend:20261019-153012-a1b2c3d`(时间 + 提交号),不会被自动清理。 `latest` 只是手动执行 compose 命令的别名,`deploy.sh` 从不依赖它。 --- ## 6. 必须记住的规则 1. **默认发布 `main`,日常开发在 `dev`** —— 直接 `./deploy.sh all` 会部署 main 上的旧代码。要么先 merge,要么 `GIT_BRANCH=dev ./deploy.sh all`。 2. **改表要向后兼容** —— 切换的几十秒里新旧代码同时在跑。加字段/加表可以;改类型、删字段要拆两次发布。 3. **`HASHID_SECRET` 上线后别动**。 4. **镜像会占磁盘**(前端约 250MB/版),每 5 版跑一次 `./deploy.sh --prune`。 --- ## 7. 管理后台 ```bash ssh -L 18092:127.0.0.1:18092 user@server # 本机浏览器打开 http://localhost:18092/admin ``` --- ## 8. 域名 / HTTPS 前端只监听 80,二选一: - **宿主机 nginx / 宝塔**:`.env` 里 `HTTP_PORT=8080`,外层反代到 `127.0.0.1:8080`,证书在外层做 - **Caddy(自动证书)**:同 compose 加 caddy 容器,`reverse_proxy 127.0.0.1:80` --- ## 9. 备份 ```bash docker compose exec -T db pg_dump -U fashion fashion | gzip > backups/db-$(date +%F).sql.gz ``` 每天 4 点自动备份并保留 7 天: ``` 0 4 * * * cd /opt/fashion/backend && docker compose exec -T db pg_dump -U fashion fashion | gzip > backups/db-$(date +\%F).sql.gz && find backups -name 'db-*.sql.gz' -mtime +7 -delete ``` --- ## 10. 排障 ```bash docker compose ps # 各容器状态与健康 docker compose logs -f proxy # 切换失败先看这里 docker compose logs -f backend_a docker compose logs --tail 100 web_b ``` | 现象 | 原因 | | --- | --- | | 全站接口 404 | `nginx/default.conf` 的 `proxy_pass` 带了 URI/斜杠(会剥掉 `/api` 前缀) | | 详情页 500 | 前端 `BASE_API_SSR` 必须是 `http://proxy:18090`(容器里的 localhost 是它自己) | | 图片全 404 | `S4_AK/S4_SK` 没填或填错 | | 前端构建失败 | 构建期要连 18091 取 SSG:确认 `proxy` 在跑、`backend_a` healthy | | 发布后仍是旧版本 | 分支不对(默认 `main`,日常在 `dev`);或 `docker compose logs proxy` 里 reload 报错 | | 回滚提示"槽不可用" | 该槽被更早的发布重建过,回滚只能退一格 | | 登录后跳回登录页 | `JWT_SECRET` 改过,旧 token 失效 | | 磁盘涨得快 | `./deploy.sh --prune` |