讲解

真实应用栈的典型形态:一个对外的 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 控制就绪顺序、命名卷持久化。这套形态覆盖大多数单机部署。下一章讲镜像从哪来、到哪去:仓库与标签。