1. Docker Compose:把「一堆容器」变成「一个产品」
单容器适合 demo;真实项目几乎总有 Web、数据库、缓存、队列。Compose 的价值是:用声明式 YAML 描述谁连谁、数据放哪、谁先起, 而不是每人手写一长串 docker run。
1.1 核心概念
- services:每个服务一个容器(或 scale 多副本)。
- networks:默认同一 project 网络内服务可通过服务名 DNS 互访。
- volumes:命名卷持久化 DB 数据;bind mount 把本机代码挂进容器做热重载。
- depends_on:表达依赖关系;配合 healthcheck 可避免「DB 还没 ready 应用就 crash」。
常见坑:
depends_on 只保证启动顺序,不保证依赖服务已可接受连接。生产级本地栈请给 DB 配 healthcheck,应用侧用重试或 wait 脚本。
图 1:典型 Compose 栈。Web 通过服务名访问 db/redis;DB 用命名卷持久化;开发时 Web 可 bind mount 源码实现热重载。
2. 多阶段构建:builder 胖、runtime 瘦
第一阶段装编译器、跑测试、打静态资源;最终镜像只拷贝产物与运行时依赖。这样攻击面小、拉取快、部署省带宽。 在 Dockerfile 里用 AS builder / FROM ... AS runtime,Compose 里可用 target: 只构建 dev stage 做本地调试。
- COPY --from=builder:只把二进制或 dist 拷进最终层。
- 非 root 用户:最终 stage 用 USER appuser。
- 同一 Dockerfile 多 target:docker compose build --target dev 拉全工具链;生产用默认 final stage。
图 2:多阶段构建。构建阶段可任意臃肿;运行镜像只保留必要文件,体积与风险面同步下降。
3. 构建缓存与 BuildKit:让「改一行代码」别重装全世界
经典优化:先 COPY package.json package-lock.json 再 RUN npm ci,后 COPY . .,这样改业务代码不会 bust 依赖层。 开启 BuildKit 后可用 cache mount(如 pip/npm 缓存目录挂载到缓存卷),在 CI 无层缓存时仍能显著加速。
- .dockerignore:排除 .git、node_modules、测试产物,减小 context hash 变化频率。
- ARG 与缓存:频繁变化的 build-arg 会 invalidate 后续层,尽量少用或后置。
- compose build cache:CI 可推送/拉取 registry cache(视平台支持)。
口诀:「依赖锁定文件先进镜像 → 装依赖 → 再拷源码」。违反顺序 = 每次构建都像第一次。
4. 本地交付契约:.env、override、与生产对齐
- 仓库根提供 .env.example(全英文 key + 中文注释可写在 README),列出必填变量。
- docker-compose.override.yml(gitignore)放个人机器差异;团队共享 compose.dev.yml。
- 机密不进 Git:用 env_file 或运行时注入,与第 19 章「secret 与 config 分离」一致。
- 生产镜像与本地尽量同一 Dockerfile final stage,避免「本地能跑、线上另一套」。
图 3:本地交付闭环。配置一次环境变量后,compose 拉起全栈;bind mount 让开发循环留在宿主机编辑器里完成。
5. 最小清单(可直接当团队规范)
- 根目录:docker-compose.yml + .env.example + README 一段「如何 up」。
- DB/Redis:healthcheck + 命名卷;Web:depends_on condition。
- Dockerfile:多阶段 + 正确层顺序 + .dockerignore。
- CI:同一 Dockerfile 构建;可选 BuildKit cache。
自检三问:
1) 新人是否只需 copy env + compose up?
2) 改一行业务代码会不会触发全量依赖安装?
3) 本地跑的镜像与生产 final stage 是否同源?
1) 新人是否只需 copy env + compose up?
2) 改一行业务代码会不会触发全量依赖安装?
3) 本地跑的镜像与生产 final stage 是否同源?