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

关键参数说明:

参数 含义 建议值 interval 两次检查间隔 10-30s timeout 单次检查超时 5-10s retries 连续失败几次才算不健康 3-5 start_period 启动后的豁免期,不计数失败 10-30s

start_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大叔的工具箱,每周分享实用技术干货。

类似文章