Files
book-comic-library/docs/superpowers/specs/2026-09-07-docker-deploy-spec-design.md
T

9.0 KiB
Raw Blame History

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;smoke.sh 里依赖旧 ./library:/data/books 绑定的宿主路径 library/smoke-books(建目录与删除断言两处)同步改为 deploy/api/storage/smoke-books。
  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 全绿。