docs(spec): docker deployment conformance redesign (dev/release compose, prepare.sh, shared volumes, storage path)

This commit is contained in:
2026-09-07 19:36:34 +08:00
parent 22ae1717f4
commit e563c88913
@@ -0,0 +1,114 @@
# Docker 部署规范化改写 — 设计文档
日期:2026-09-07 · 状态:已确认(用户批准) · 范围:仅部署形态,不改业务架构
## 1. 背景与目标
现有部署(根 `docker-compose.yml` + `deploy/docker-compose.dev.yml`)与团队 Docker 部署规范不一致。本改造把 booklib 重写为规范形态:
- dev = `deploy/docker-compose.dev.yml`,release = `deploy/docker-compose.yml`(根 compose 删除)
- 两模式共享 named volumes,`down -v` 才清数据
- 配置由 `prepare.sh` 从模板渲染后 `:ro` 挂载进容器
- dev 源码热挂载 + `target: dev` 镜像 + delve :2345;release 用 `*.prod` Dockerfile(`target: runner`)编译产物,不挂源码
- 文件存储挂 `deploy/api/storage`(对齐 `deploy/{service_name}/storage` 约定)
**非目标**:不引入 rabbitmq/consul/minio/ES,不拆分 worker,不改业务代码(除 §7 列出的两处适配)。
## 2. 目录布局
```
deploy/
docker-compose.yml # release
docker-compose.dev.yml # dev(重写:四服务全容器化)
prepare.sh # 渲染配置,up 之前必须执行
.env.example # 从仓库根迁入;本地 .env 同位置(项目目录=deploy/,插值才生效)
templates/
nginx.conf.tpl # nginx 主配置(events/http、include conf.d、日志与 pid 路径)
default.conf.tpl # server 块(CSP、/api 反代、SPA fallback、resolver 占位行)
redis.conf.tpl # maxmemory / policy
nginx/nginx.conf # 渲染产物(gitignore)
nginx/conf.d/default.conf # 渲染产物(gitignore)
redis/redis.conf # 渲染产物(gitignore)
logs/nginx/ # prepare.sh 创建;宿主机收集 nginx access/error log(gitignore)
api/storage/ # 书库+缓存绑定挂载(gitignore,保留 .gitkeep)
Dockerfile.api.dev # 多阶段,target: dev(golang + delve)
Dockerfile.api.prod # 多阶段,target: runner(静态二进制)
Dockerfile.web.dev # 多阶段,target: dev(node,跑 vite)
Dockerfile.web.prod # 多阶段,target: runner(构建 SPA + nginx)
entrypoint-resolver.sh # 保留并调整:见 §5
```
删除:根 `docker-compose.yml`、根 `.env.example`、旧 `deploy/Dockerfile.api`、旧 `deploy/Dockerfile.web`、旧 `deploy/nginx.conf`。`library/` 目录废弃(当前为空且 gitignore)。
## 3. 项目名与共享卷
- 两个 compose 顶层显式 `name: booklib`,与运行目录解耦。
- 卷:`postgres_data`、`redis_data` → 实际名 `booklib_postgres_data`、`booklib_redis_data`;两文件完全同名,dev/release 看到同一份数据。仅 `down -v`(rebuild/clear)清除。
- redis 按规范挂 `redis_data` 到 `/data`(原「redis 不落盘」设计让位于规范;只是多持久化一份可再生缓存,无功能副作用)。
- 旧卷迁移(README 记录一次性命令):
`docker run --rm -v book-comic-library_pgdata:/from -v booklib_postgres_data:/to alpine cp -a /from/. /to/`
旧 `cache` 卷为封面/解压缓存,可直接丢弃(自动重建)。
## 4. dev 模式(`deploy/docker-compose.dev.yml`)
| 服务 | 镜像/build | 挂载 | 端口(宿主:容器) | 启动 |
|---|---|---|---|---|
| postgres | postgres:16-alpine | volume `postgres_data` | 5432:5432 | 默认 |
| redis | redis:7-alpine | `./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro`、`redis_data:/data` | 6379:6379 | `redis-server /usr/local/etc/redis/redis.conf`(移除命令行 maxmemory) |
| api | `Dockerfile.api.dev` target `dev` | `../backend:/app`、`./api/storage:/data/books` | 2345:2345(delve) | `go mod download && dlv debug ./cmd/server --headless --listen=0.0.0.0:2345 --api-version=2 --log` |
| web | `Dockerfile.web.dev` target `dev` | `../web:/app` + 匿名卷 `/app/node_modules`(镜像内依赖不被覆盖) | 5173:5173 | `npm run dev -- --host 0.0.0.0` |
- api env:`DATABASE_URL=postgres://lib:lib@postgres:5432/lib?sslmode=disable`、`REDIS_URL=redis://redis:6379`、`BOOKS_DIR=/data/books`、`CACHE_DIR=/data/books/cache`、`JWT_SECRET/ADMIN_USER/ADMIN_PASSWORD/SCAN_INTERVAL_SEC` 自 `.env` 插值。
- 启动门控:api `depends_on` postgres `service_healthy` + redis `service_started`;web 不依赖 api 健康(vite 反代容忍后端重启)。
- 热更新语义:改 Go/TS 源码 → `docker compose ... restart api|web` 即生效(go run/dlv 重编译、vite 本身 HMR),全程无需 rebuild 镜像。
- dev 不起 nginx(vite 代理承担 `/api`)。因此 nginx 配置/log 绑定挂载只在 release 出现——这是对规范「两模式都挂 nginx 配置」的有意偏差:dev 无 nginx 进程,挂之无意义。
- 删容器用 `down`,不用 `rm`,避免匿名 node_modules 卷成为孤儿。
## 5. release 模式(`deploy/docker-compose.yml`)
- web:`Dockerfile.web.prod` target `runner`;宿主端口 `${WEB_PORT:-8080}:80`;挂载
`./nginx/nginx.conf:/etc/booklib/nginx.conf:ro`、`./nginx/conf.d/default.conf:/etc/booklib/default.conf:ro`、`./logs/nginx:/var/log/nginx`。
配置挂到 `/etc/booklib/` 暂存而非直接挂 `/etc/nginx/conf.d/`,因为 `:ro` 绑定挂载下 entrypoint 的 sed 无法原地改写。
- `entrypoint-resolver.sh` 调整:启动时 `cp /etc/booklib/default.conf → /etc/nginx/conf.d/default.conf`,再按 `/etc/resolv.conf` 首个 nameserver sed 注入 resolver,然后 exec nginx。镜像不再 COPY baked 配置(`Dockerfile.web.prod` 移除该 COPY;若忘跑 prepare.sh,文件缺失会让 compose 启动即报错,符合规范「stale/缺失 = 忘了重跑 prepare」)。
- api:`Dockerfile.api.prod` target `runner`;无源码挂载、无配置挂载(本项目 api 配置全部来自 env,符合「Go 服务只挂配置文件」——无可挂项即为不挂);`./api/storage:/data/books` 与 dev 同路径同数据;`BOOKS_DIR=/data/books`、`CACHE_DIR=/data/books/cache`。
- postgres/redis:不暴露宿主端口,仅容器网络;redis 同样挂渲染 `redis.conf` + `redis_data`。
- 渲染产物 bind mount 集合与规范对齐:nginx 两份 + redis.conf + logs/nginx。
## 6. prepare.sh 与模板变量
`deploy/prepare.sh`(`set -eu`,在脚本自身目录执行):
1. 若存在 `deploy/.env` 则逐行解析 `KEY=VALUE` 注入渲染环境;未定义变量用下表默认值兜底。
2. `mkdir -p nginx/conf.d redis logs/nginx api/storage`。
3. 用 sed 把模板中 `{{VAR}}` 替换为实际值,渲染到对应产物路径(幂等,可反复执行)。
| 变量 | 默认值 | 用途 |
|---|---|---|
| `WEB_PORT` | 8080 | release 宿主端口 |
| `NGINX_CLIENT_MAX_BODY_SIZE` | 200m | default.conf.tpl |
| `REDIS_MAXMEMORY` | 128mb | redis.conf.tpl |
| `REDIS_MAXMEMORY_POLICY` | allkeys-lru | redis.conf.tpl |
| `DELVE_PORT` | 2345 | dev compose 的 delve 宿主端口(非模板变量,供 `${DELVE_PORT:-2345}` 插值) |
现有 `.env` 键(JWT_SECRET/ADMIN_USER/ADMIN_PASSWORD/SCAN_INTERVAL_SEC)不变;新增上表键写入 `.env.example`。
## 7. 周边代码改动(最小集)
1. `web/vite.config.ts`:proxy target 改为 `process.env.VITE_PROXY_TARGET ?? "http://localhost:8080"`;dev compose 给 web 服务注入 `VITE_PROXY_TARGET=http://api:8080`。宿主机跑 vite 的旧习惯不受影响。
2. `scripts/smoke.sh` / `scripts/smoke-web.sh`:`. ./.env` 改为优先 `deploy/.env`、回退根 `.env`。
3. `.gitignore`:删 `library/`,增 `deploy/.env`、`deploy/nginx/`、`deploy/redis/redis.conf`、`deploy/logs/`、`deploy/api/storage/*`(`!.gitkeep`)。
4. `README.md`:dev/release 命令全部重写(`docker compose -f deploy/docker-compose.yml|docker-compose.dev.yml`,先 `deploy/prepare.sh`),旧卷迁移说明,dev 调试(delve :2345 / IDE attach 示例)。
## 8. 错误处理
- 未跑 prepare.sh 就 `up`:compose 绑定挂载源不存在 → Docker 直接报错(不是静默空目录);`prepare.sh` 本身缺 `.env` 不报错(用默认值渲染),缺模板则报错退出。
- api 连不上 PG:healthcheck 门控 + 后端已有启动失败即退出的行为不变。
- delve 端口冲突:`2345` 可经 `.env` 变量 `DELVE_PORT`(默认 2345)覆盖。
## 9. 验收
1. `deploy/prepare.sh && docker compose -f deploy/docker-compose.yml up -d --build` → `bash scripts/smoke.sh` 与 `bash scripts/smoke-web.sh` 全绿(端口语义与现在一致:宿主 8080)。
2. `docker compose -f deploy/docker-compose.dev.yml up -d --build` → `curl localhost:5173` 返回 vite 页面、`curl localhost:5173/api/healthz` 经代理 200;宿主 `nc -z localhost 2345` 通。
3. 数据共享验证:dev 建库上传 → dev `down` → release `up` → 数据可见;`docker volume ls | grep booklib_` 两卷名一致。
4. 规范符合性核对表逐项打勾(挂载、target、named volume、prepare 产物、存储路径)。
5. 回归:`cd backend && go vet ./... && go test -p 1 -count=1 ./...`、`cd web && npm run check` 全绿。