Docker Compose 高级编排完全指南:健康检查、网络策略、多阶段构建与生产级部署
Docker Compose 生产级编排实战。健康检查解决启动顺序、双网络隔离保障安全、多阶段构建镜像瘦身80%、5个必配参数防故障,IT大叔两年落地经验全分享。
Docker Compose 高级编排完全指南:健康检查、网络策略、多阶段构建与生产级部署
大家好,我是IT大叔。
上一篇文章(2026 Docker 入门到实战)发完之后,收到十几条私信,问的都是同一个问题——单容器玩明白了,多服务编排怎么搞?
我自己的博客就是 Nginx + PHP-FPM + MariaDB 三容器架构,刚开始也踩了不少坑。比如:数据库还没就绪,PHP 就抢先启动导致报错;日志没限制,三天撑爆 20G 磁盘;网络配错,容器之间死活连不上。
今天这篇就把这些坑一次性填平。 不讲虚的,只看四个实战场景:Compose 进阶编排、健康检查与依赖管理、网络隔离策略、多阶段构建优化。每个场景都在我生产环境跑了两年以上,是真正的落地经验。
一、Compose 文件模板升级:从能跑到跑稳
上篇文章的 docker-compose.yml 能跑,但离”稳”还差几步。下面是我博客的生产级 Compose 模板,你看一眼就知道差距在哪儿:
services:
db:
image: mariadb:10.11
volumes:
- db_data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
MYSQL_DATABASE: ${DB_NAME}
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASS}
# === 进阶配置从这里开始 ===
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- backend
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
app:
image: wordpress:6.7-php8.3-fpm
volumes:
- wp_data:/var/www/html
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: ${DB_USER}
WORDPRESS_DB_PASSWORD: ${DB_PASS}
WORDPRESS_DB_NAME: ${DB_NAME}
restart: unless-stopped
# 关键:等待数据库健康检查通过
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "php", "-m"]
interval: 30s
timeout: 10s
retries: 3
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- backend
nginx:
image: nginx:1.25-alpine
ports:
- "${HTTP_PORT:-80}:80"
- "${HTTPS_PORT:-443}:443"
volumes:
- wp_data:/var/www/html:ro
- ./nginx:/etc/nginx/conf.d:ro
depends_on:
app:
condition: service_started
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
networks:
- frontend
- backend
volumes:
db_data:
wp_data:
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 后端网络不对外暴露
五个关键升级点:
1. .env 变量化 — 密码、端口、数据库名写在 .env 文件里,Compose 自动读取。从此 docker compose config 可以看到最终渲染结果,密码不会暴露在 Git 仓库中。
2. healthcheck 健康检查 — 比 depends_on 裸用强百倍。condition: service_healthy 让 app 容器等到数据库真正能响应才启动。
3. deploy.resources.limits — 限制每个容器的内存和 CPU,防止某个服务把宿主机资源吃光。
4. logging 限制 — 每容器最多保留 3 个日志文件,每个 10MB,再多也不爆盘。
5. 双网络隔离 — Nginx 挂载前、后两个网络,app 和 db 只在 internal: true 的后端网络通信,外界无法直接访问数据库。
二、健康检查实战:彻底解决服务启动顺序问题
这是 Docker Compose 里最大的坑,没有之一。你回想一下,是不是遇到过这种情况:
- 数据库还没完全初始化,应用就连接了 → Connection refused
- Redis 还没就绪,业务代码请求了 → 缓存雪崩
- 迁移脚本还没跑完,新版容器就启动了 → 数据不一致
depends_on 本身只能保证容器启动顺序,不能保证服务可用。 这才是真方案:
2.1 内置健康检查(Dockerfile 级)
最简单的做法,在 Dockerfile 里加 HEALTHCHECK 指令:
FROM node:20-alpine
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "server.js"]
2.2 Compose 级健康检查(推荐)
更灵活的做法是在 docker-compose.yml 里声明:
services:
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 3
start_period: 10s
api:
build: .
depends_on:
redis:
condition: service_healthy
关键参数说明:
intervaltimeoutretriesstart_periodstart_period 是必配项。 没有它,如果应用启动需要 30 秒,健康检查可能在 5 秒后连续失败 3 次直接标记为 unhealthy,然后被 restart 策略重启——形成死循环。
三、网络策略:给容器装上防火墙
默认情况下,同一个 Compose 项目中的所有容器都在同一个 bridge 网络上,彼此全通。这在生产环境中很危险——如果你的数据库暴露在了所有服务面前,等于把保险柜的钥匙挂在门口。
3.1 双网络隔离(默认推荐)
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # ← 不能访问外网
services:
nginx:
networks:
- frontend
- backend
app:
networks:
- backend
db:
networks:
- backend
这样 Nginx 是唯一连接两个网络的服务,流量路径是:
用户 → frontend → Nginx → backend → app → backend → db
即使有人攻破了 app 容器,也无法从 backend 网络访问外网(internal: true)。
3.2 自定义 IP 与 DNS
当你有严格的 ACL 需求时,给容器分配固定 IP:
services:
db:
networks:
backend:
ipv4_address: 10.10.0.10
networks:
backend:
driver: bridge
ipam:
config:
- subnet: "10.10.0.0/24"
3.3 跨主机通信(Swarm / Overlay)
多台服务器部署时,用 overlay 网络:
networks:
my_overlay:
driver: overlay
attachable: true
不过说实话,如果你的节点少于 3 台,别折腾 Swarm。Tailscale + 普通 bridge 网络反而是更稳的方案。
四、多阶段构建:把镜像瘦身 80%
很多人的 Dockerfile 写出来镜像超过 1GB,推送慢、拉取慢、占用磁盘空间。用多阶段构建轻松瘦身。
实战:Go 应用构建
# 第一阶段:编译
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp .
# 第二阶段:运行(只有可执行文件,不到 20MB)
FROM alpine:3.19
RUN apk --no-cache add ca-certificates tzdata
COPY --from=builder /app/myapp /usr/local/bin/myapp
EXPOSE 8080
CMD ["myapp"]
最终镜像大小对比:
- 单阶段:golang:1.22-alpine 基础镜像 ~350MB + 编译产物 ≈ 400MB+
- 多阶段:alpine:3.19 基础镜像 ~5MB + 可执行文件 ≈ 20MB
Python 应用多阶段构建
# 第一阶段:安装依赖
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 第二阶段:只复制 site-packages
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY . .
CMD ["python", "main.py"]
这样可以缓存依赖层,改代码时不需要重装依赖,构建只需 3-5 秒。
五、生产环境必须配的 5 个 Compose 参数
这些配置项是从事故中趟出来的经验,每一个都对应过一个真实故障。
1. restart: unless-stopped
别用 always。区别在于:如果你手动 docker compose stop 一个服务,always 会立刻重启它(你刚停它就拉起来),而 unless-stopped 尊重你的手动停止。
2. deploy.resources.limits
deploy:
resources:
limits:
memory: 512M
cpus: "0.5"
reservations:
memory: 256M
limits 是硬上限,reservations 是保证值。生产环境必须配 limits,否则一个内存泄漏的服务能拖垮整台机器。
3. logging 限制
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
不配的话,json-file 驱动默认无限增长。 我一个朋友的服务跑了两个月,日志文件 47GB,排查磁盘报警才发现。
4. read_only: true
services:
app:
read_only: true
tmpfs:
- /tmp
把容器的根文件系统设为只读,攻破容后的攻击者无法写文件。配合 tmpfs 挂载临时目录存放运行时缓存。
5. security_opt: no-new-privileges
services:
app:
security_opt:
- no-new-privileges:true
防止容器内进程通过 suid 二进制文件提权。这是安全基线配置,应该加到每个服务上。
六、日常运维三板斧
6.1 快速定位问题
# 查看所有服务状态
docker compose ps
# 看某个服务的实时日志
docker compose logs -f app
# 进入正在运行的容器排查
docker compose exec app bash
# 查看 Compose 最终渲染的配置(展开所有变量)
docker compose config
docker compose config 是我的最爱——它把 .env 变量全部展开,打印出最终的 YAML。当你怀疑变量没设置对时,这条命令一秒出答案。
6.2 零停机更新
# 拉取新镜像并重建服务,保留旧容器
docker compose pull app
docker compose up -d --no-deps --scale app=2 app
# 确认新版正常后,清理旧版
docker compose up -d --no-deps app
通过 --scale 先扩容到 2 个实例完成滚动切换,用户几乎感知不到重启。
6.3 一键备份与恢复
# 备份数据卷
docker run --rm -v db_data:/source -v $(pwd):/backup alpine \
tar czf /backup/db-backup-$(date +%Y%m%d).tar.gz -C /source .
# 恢复
docker run --rm -v db_data:/target -v $(pwd):/backup alpine \
tar xzf /backup/db-backup-20260705.tar.gz -C /target
数据卷备份不需要停容器,但强烈建议在备份前停写操作(WordPress 可以临时关掉评论和写入功能)。
七、总结
回顾一下这篇的核心要点:
- 健康检查 + condition: service_healthy 替代裸
depends_on,解决启动顺序问题 - 双网络隔离(frontend / backend)是安全基线,internal: true 把数据库藏起来
- 多阶段构建把镜像从 400MB 瘦到 20MB,推送快、启动快、省磁盘
- 5 个必配参数:unless-stopped、资源限制、日志上限、read_only、no-new-privileges
- 日常三板斧:compose config 展开变量、滚动更新不停服、卷备份保数据
上篇文章我教了 Docker 怎么跑起来,这篇教你怎么跑稳、跑久、跑安全。技术这东西,从能用到用好,差的往往就是这些细节。
下一篇我会写 Docker Swarm / Nomad 集群部署实战,如果你有 3 台以上的服务器,那是把 Docker Compose 的编排能力放大到集群维度的下一站。
有疑问欢迎留言,每条我都会看。关注 IT大叔的工具箱,每周分享实用技术干货。
