9.0 KiB
9.0 KiB
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 用*.prodDockerfile(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_onpostgresservice_healthy+ redisservice_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.prodtargetrunner;宿主端口${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.prodtargetrunner;无源码挂载、无配置挂载(本项目 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,在脚本自身目录执行):
- 若存在
deploy/.env则逐行解析KEY=VALUE注入渲染环境;未定义变量用下表默认值兜底。 mkdir -p nginx/conf.d redis logs/nginx api/storage。- 用 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. 周边代码改动(最小集)
web/vite.config.ts:proxy target 改为process.env.VITE_PROXY_TARGET ?? "http://localhost:8080";dev compose 给 web 服务注入VITE_PROXY_TARGET=http://api:8080。宿主机跑 vite 的旧习惯不受影响。scripts/smoke.sh/scripts/smoke-web.sh:. ./.env改为优先deploy/.env、回退根.env;smoke.sh里依赖旧./library:/data/books绑定的宿主路径library/smoke-books(建目录与删除断言两处)同步改为deploy/api/storage/smoke-books。.gitignore:删library/,增deploy/.env、deploy/nginx/、deploy/redis/redis.conf、deploy/logs/、deploy/api/storage/*(!.gitkeep)。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. 验收
deploy/prepare.sh && docker compose -f deploy/docker-compose.yml up -d --build→bash scripts/smoke.sh与bash scripts/smoke-web.sh全绿(端口语义与现在一致:宿主 8080)。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通。- 数据共享验证:dev 建库上传 → dev
down→ releaseup→ 数据可见;docker volume ls | grep booklib_两卷名一致。 - 规范符合性核对表逐项打勾(挂载、target、named volume、prepare 产物、存储路径)。
- 回归:
cd backend && go vet ./... && go test -p 1 -count=1 ./...、cd web && npm run check全绿。