讲解
真实应用栈的典型形态:一个对外的 web 服务 + 一个只对内开放的数据库。本章用 nginx(web)+ MySQL 8(db)把这个形态跑起来,重点讲清三个协作机制。一是内部网络:Compose 自动建的网络里,web 用主机名 db 直连 MySQL 的 3306;db 不做 ports 映射,宿主机和其他机器都摸不到它——对比「db 也映射端口到宿主机」的做法,安全性高下立判。
二是启动顺序与健康检查:depends_on 默认只保证「先启动」,不保证「先就绪」——MySQL 初始化要几十秒,web 一起动就连接会撞「connection refused」。正确做法是给 db 配 healthcheck,web 的 depends_on 用 condition: service_healthy,Compose 会等 db 真正可用再拉起 web。健康检查的命令也有讲究:mysqladmin ping 在引导阶段就会响应,此时业务库和业务用户可能还没创建好——用业务用户执行 SELECT 1 才是「真就绪」。这是 Compose v2 最重要的编排能力之一。
三是数据持久化:db 的数据目录挂命名卷(volumes 顶层声明),down 之后再 up 数据还在;环境变量通过 environment 传入(生产密钥走 .env 文件或 secret,别硬编码)。这套「健康检查 + 内部网络 + 命名卷」的组合就是单机生产部署的及格线。
示例
先准备 web 服务的静态页面(构建上下文在沙盒里):
mkdir -p site && printf '<h1>web 服务正常</h1><p>db 在同一网络, 主机名 db</p>\n' > site/index.html
双服务 compose 文件:db 健康检查 + web 条件依赖 + 命名卷持久化(yaml 块会被真实启动):
# project: stack
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: tutorial-root
MYSQL_DATABASE: shop
MYSQL_USER: shopper
MYSQL_PASSWORD: shopper-pass
volumes:
- dbdata:/var/lib/mysql
healthcheck:
test: ["CMD", "mysql", "-ushopper", "-pshopper-pass", "shop", "-e", "SELECT 1"]
interval: 5s
timeout: 5s
retries: 30
web:
image: nginx:1.27-alpine
volumes:
- ./site:/usr/share/nginx/html:ro
ports:
- "18086:80"
depends_on:
db:
condition: service_healthy
volumes:
dbdata:
Compose 启动时会等 db 健康再拉起 web。验证 web 可用,并确认 db 按服务名可解析、可连接(从 web 容器里 wget 不了 MySQL 协议,用一次性容器验证更直接):
curl -sf http://localhost:18086 | head -2
docker exec ohmydocs-verify-stack-db-1 sh -c 'mysql -uroot -ptutorial-root -e "SELECT VERSION();" 2>/dev/null' | tail -1
确认 db 没有暴露端口给宿主机(PORTS 列应为空):
docker ps --filter name=ohmydocs-verify-stack --format '{{.Names}} 端口=[{{.Ports}}] 状态={{.Status}}'
数据卷的生命周期独立于容器——down 之后卷还在(Compose 会在拆除时保留命名卷,除非 down -v;本教程清理阶段统一处理):
docker volume ls --filter name=ohmydocs-verify-stack --format '{{.Name}}'
常见坑
- depends_on 不加条件:只保证启动顺序不保证就绪;应用没有重连逻辑就会在数据库初始化期间崩溃——healthcheck + service_healthy 是标配。
- db 也映射端口:内部服务零暴露;调试需要临时连接时用 docker compose exec db mysql 而不是开端口。
- 环境变量里的特殊字符:YAML 里密码含 #、: 要加引号;含 $ 会被插值,需写成 $$ 转义。
- 健康检查命令镜像里没有:mysqladmin、wget、curl 不是所有镜像都带,写 healthcheck 前先确认镜像里有什么。
小结
多服务三件套:内部网络按名互访、healthcheck + service_healthy 控制就绪顺序、命名卷持久化。这套形态覆盖大多数单机部署。下一章讲镜像从哪来、到哪去:仓库与标签。