Compose 进阶:networks、depends_on、healthcheck 玩转编排
Compose 入门之后,下一步是把编排写扎实。小够讲透自定义网络、服务依赖的正确姿势、健康检查、env_file、profiles、多文件覆盖和 secrets,附一份生产可用的 compose 模板。
我是小够,只推够用的 VPS。
入门篇的 compose 文件能跑,但离"生产可用"还差几口气:服务隔离、数据库就绪后再起应用、密码别硬编码、多环境切换。今天把这几块补齐,最后给一份小够自己用的模板。
自定义网络:给服务分组
默认情况下,一个 compose 项目里的所有服务都在同一个网络,能互相 ping 通。应用一多,你可能想隔离:比如网站 A 的数据库,不想让网站 B 碰到。
services:
web:
image: nginx:alpine
networks:
- frontend
db:
image: mysql:8.0
networks:
- backend
api:
image: myapi:latest
networks:
- frontend
- backend
networks:
frontend:
backend:
api 同时在两个网络,能跟 web 和 db 说话;web 和 db 互相隔离。应用不多时可以不用,但迟早用得上。
网络还有个实用选项 internal: true,表示这个网络不通外网。数据库放 internal 网络,连外网都出不去——零成本的安全提升。
自定义网络还能让跨项目的服务互联:两个 compose 项目默认网络隔离,但可以加入同一个外部网络:
networks:
shared:
external: true
先 docker network create shared 建好,两个项目的服务都加入,就能互相访问。适合"监控容器看到所有项目"这种场景。
depends_on 的正确姿势
depends_on 只保证启动顺序,不等服务就绪。“等数据库 ready 再起应用"靠 healthcheck:
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: 强密码
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p强密码"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
wordpress:
image: wordpress:6-php8.2-apache
depends_on:
db:
condition: service_healthy
参数解释:检查命令 / 每 10 秒查一次 / 单次 5 秒超时 / 连失败 5 次判不健康 / 30 秒宽限期(1 核小机器把这个设大点,初始化慢)。
Redis 的健康检查一行就行:test: ["CMD", "redis-cli", "ping"]。
docker ps 的 STATUS 列会显示 (healthy),一眼看出谁有问题;配合 restart: unless-stopped,不健康会自动重启恢复。
敏感信息:env_file
密码别硬编码在 compose 文件里。建个 .env 文件(和 compose 文件同目录,加进 .gitignore):
MYSQL_ROOT_PASSWORD=强密码1
MYSQL_PASSWORD=强密码2
compose 文件里引用:
services:
db:
image: mysql:8.0
env_file:
- .env
environment:
MYSQL_DATABASE: wordpress
env_file 整文件导入,environment 单个写,混用时后者优先级更高。换机器部署,compose 文件直接拷,.env 单独传,权限设 600:
chmod 600 .env
profiles:一个文件,多种组合
services:
web:
image: nginx:alpine
profiles: ["prod"]
debug-tools:
image: nicolaka/netshoot
profiles: ["debug"]
默认只启动没有 profiles 的服务;docker compose --profile debug up -d 才把调试工具拉起来。测试、生产共用一份文件。netshoot 是网络调试瑞士军刀,排错时临时用,用完就停。
多文件覆盖:开发和生产分开
# docker-compose.yml(公共部分)
services:
web:
image: nginx:alpine
# docker-compose.override.yml(本地开发专用)
services:
web:
ports: ["8080:80"]
Compose 会自动合并 docker-compose.yml 和 docker-compose.override.yml。生产环境用 -f 指定文件,避开 override:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
多个 -f 按顺序合并,后面的覆盖前面的。开发、测试、生产各一个覆盖文件,公共部分只维护一份。
configs:只读配置的优雅挂法
services:
web:
image: nginx:alpine
configs:
- source: nginx-conf
target: /etc/nginx/nginx.conf
configs:
nginx-conf:
file: ./nginx.conf
和绑定挂载效果类似,但语义更清晰:“这是配置,不是数据”。单机用绑定挂载也行,个人用户不用纠结。
资源限制:别让一个服务吃光机器
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
memory: 256M
limits 是上限,reservations 是预留。记住给数据库和应用都设上——MySQL 内存泄漏起来比应用还凶。
一份生产可用的模板
把上面全串起来。注意模板里每个服务都带了日志配置:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
长期跑的服务一定要配,不然磁盘迟早被日志撑满。
services:
db:
image: mysql:8.0
restart: unless-stopped
env_file: [.env]
volumes:
- db-data:/var/lib/mysql
networks: [backend]
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"
deploy:
resources:
limits:
memory: 1G
web:
image: nginx:alpine
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports: ["8080:80"]
volumes:
- ./html:/usr/share/nginx/html:ro
networks: [frontend, backend]
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 256M
networks:
frontend:
backend:
volumes:
db-data:
./html:/usr/share/nginx/html:ro 后面的 ro 是只读挂载——Nginx 只读网站文件不写,安全加分。
实战:五服务全家桶
看一个完整例子:反代 + 网站 + API + 数据库 + 缓存。
services:
proxy:
image: nginx:alpine
restart: unless-stopped
ports: ["80:80", "443:443"]
volumes:
- ./proxy.conf:/etc/nginx/conf.d/default.conf:ro
- ./certs:/etc/nginx/certs:ro
networks: [frontend]
depends_on: [web, api]
web:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./html:/usr/share/nginx/html:ro
networks: [frontend]
logging:
driver: json-file
options: { max-size: "10m", max-file: "3" }
api:
image: myapi:1.2
restart: unless-stopped
env_file: [.env]
depends_on:
db: { condition: service_healthy }
cache: { condition: service_healthy }
networks: [frontend, backend]
deploy:
resources:
limits: { memory: 512M }
db:
image: mysql:8.0
restart: unless-stopped
env_file: [.env]
volumes: [db-data:/var/lib/mysql]
networks: [backend]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
deploy:
resources:
limits: { memory: 1G }
cache:
image: redis:7-alpine
restart: unless-stopped
networks: [backend]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
deploy:
resources:
limits: { memory: 256M }
networks:
frontend:
backend:
internal: true
volumes:
db-data:
这个拓扑里:proxy 是唯一对外入口;web 和 api 挂 frontend;db 和 cache 藏在 internal 的 backend 里,连外网都出不去;api 横跨两个网络当桥梁。网络分层 + 最小暴露,这就是生产编排的核心思想。
常见问题
Q:depends_on 加 condition 的写法,老版本 Compose 支持吗?
A:需要 Compose v2.1+。认准用 docker compose(空格)就行,docker-compose(短横线那个 v1)不支持。
Q:healthcheck 一直 unhealthy,怎么查?
A:docker inspect 容器名 | grep -A 10 Health 看最后一次检查的输出。常见原因是 test 命令写错,或 start_period 给太短、服务还没初始化完就被判失败。注意 MySQL 的 -p 后面直接跟密码,中间别加空格。
Q:networks 有必要每个项目都自定义吗? A:没必要。单个应用的默认网络就够了,等一台机器跑三四个项目、想隔离数据库时再用。
Q:这份模板能直接上生产吗?
A:个人博客、小应用可以。真要上生产,再加备份、监控告警、HTTPS——模板只解决"跑得稳”,不是"万无一失"。.env 异地再存一份。
Q:profiles 和多文件覆盖用哪个? A:不冲突,可以一起用。profiles 管"同一文件里的可选服务",多文件管"不同环境的差异配置"。小项目两个都不用也行——YAGNI。