05 · Compose 编排(Docker Compose)

📅 预计 60 分钟 | ⭐ = 重要知识点 | 📌 中英术语见文末
🛠 本讲命令基于 Docker Compose v2,可直接运行


5.0 先给直觉:从"点菜"到"下套餐"

一个真实的应用很少只有一个容器:前端、后端、数据库、缓存……动不动三四个。一个个 docker run 手动敲,又长又容易漏,还记不住谁依赖谁。

Compose 是 Docker 官方的多容器编排工具:把整组容器写进一份 YAML 文件("套餐菜单"),一条命令 docker compose up 全部拉起,一条命令 docker compose down 全部收起。

💡 记忆口诀:Compose = 多容器的"套餐清单";写 YAML 定义服务,up/down 一键起落。


5.1 认识 Compose

一份"服务清单"

Compose 用一份名为 compose.yaml(或 docker-compose.yaml)的 YAML 文件描述整个应用。文件里每个服务(service)通常对应一个容器(或一组相同副本)。

docker run 参数和 Compose 字段的对应关系,是理解它的捷径:

docker run 参数 Compose 字段
--name / 镜像 services.服务名 + image
-p 8080:80 ports: ["8080:80"]
-v ./html:/usr/share/nginx/html volumes: ["./html:/usr/share/nginx/html"]
-e KEY=value environment: KEY=value
--restart=always restart: always

第一个 Compose 文件 ⭐

services:
  web:
    image: nginx:1.25
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html

启动与停止:

docker compose up -d      # 后台启动整组服务
docker compose ps         # 查看这组服务的状态
docker compose down       # 停止并删除这组容器

访问 http://localhost:8080,看到的就是 ./html 目录下的静态页面——本地改文件,刷新即生效。

⚠️ 常见错误

  1. YAML 缩进用 Tab:YAML 只能用空格,Tab 会直接解析报错。编辑器务必开启"显示空白字符"。
  2. 版本号滥用:Compose v2 不需要(也不建议写)version: 字段,那是旧版遗留,写了可能告警。
  3. docker run 思维硬搬:Compose 里端口、卷都用字符串"8080:80"),YAML 会把 8080:80 当字符串处理,不加引号可能被解析成别的类型。

5.2 服务定义常用字段

一个典型的多服务示例:Web + 数据库 + Redis,这是最常见的"套餐"。

services:
  web:
    build: .
    image: myapp:1.0
    ports:
      - "8080:80"
    environment:
      DB_HOST: db
      DB_PASSWORD: secret123
    depends_on:
      - db
    restart: unless-stopped

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: appdb
    volumes:
      - dbdata:/var/lib/mysql

  redis:
    image: redis:7-alpine

volumes:
  dbdata:

逐个字段解释 ⭐:

  • image:直接用现成镜像。
  • build:用本地 Dockerfile 构建(可与 image 一起用,给产物命名)。
  • ports:端口映射,["宿主:容器"]
  • volumes:卷或绑定挂载;命名卷要在文件底部的顶层 volumes: 里声明。
  • environment:注入环境变量(对应 -e)。
  • env_file:从文件批量读取环境变量(对应 --env-file)。
  • depends_on:声明依赖关系,控制启动顺序
  • restartno / always / unless-stopped / on-failure
  • networks:指定加入哪个网络(不写则用默认项目网络)。

一个完整的 Web + 数据库实战 ⭐

用"静态页 + MySQL"组合走一遍完整流程,理解前面所有字段怎么配合。目录结构:

project/
├── compose.yaml
├── html/
│   └── index.html
└── init/
    └── init.sql        # 首次启动时执行的建表语句

compose.yaml

services:
  web:
    image: nginx:1.25
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: appdb
    volumes:
      - dbdata:/var/lib/mysql
      - ./init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 5

volumes:
  dbdata:

启动:

docker compose up -d
docker compose ps        # 两个服务都是 healthy/running

要点:

  • ./init 挂到 /docker-entrypoint-initdb.d,MySQL 镜像首次建库时会自动执行里面的 .sql 脚本(只执行一次,卷已存在则跳过)。
  • depends_on + service_healthy 保证 web 在 db 真正可连后才启动。
  • 数据都落进 dbdata 命名卷,down 不删,重建容器数据还在。

这就是"一个真实的容器化应用"的最小骨架:一个对外服务、一个数据库、一个持久卷、一份健康检查。

变量替换与校验

Compose 文件里可以用 ${变量名} 引用环境变量,适合"同一份文件,不同环境填不同值":

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-root123}   # 默认值语法 :-root123

${DB_PASSWORD:-root123} 表示:环境变量存在就用它的值,不存在就用 root123。Compose 在解析文件时会读取宿主机环境变量或同目录 .env 文件。

写完文件先检查再启动,是好习惯:

docker compose config        # 打印解析后的最终配置,校验语法

它会把变量替换、默认值、缩进错误全部暴露出来,docker compose up 之前先跑一遍能省很多报错。

⚠️ 常见错误

  1. 命名卷忘记在顶层声明dbdata: 要在文件最底部的顶层 volumes: 出现,否则报错或匿名化。
  2. 以为 depends_on 保证"数据库就绪":它只保证启动顺序(先起 db 再起 web),不保证 db 内部就绪。真正的就绪等待要靠健康检查(condition: service_healthy,第 6 讲)。
  3. 服务名和容器名混淆:Compose 里容器名会自动带项目名前缀(如 项目名-db-1);服务内通信用服务名db),不是容器名。
  4. .env 后忘了重新解析:Compose 在 up 时读取变量,改了 .env 要重新 up -d 生效,光改文件不重启没用。

5.3 常用命令

命令 作用
docker compose up -d 后台启动/重建整组服务
docker compose up -d --build 先重新构建镜像再启动
docker compose ps 查看服务状态
docker compose logs -f 实时看所有服务日志
docker compose logs web 只看 web 的日志
docker compose exec web bash 进入某个服务容器
docker compose stop 停止(不删除容器)
docker compose start 启动已停止的服务
docker compose restart 重启
docker compose build 只构建镜像,不启动
docker compose down 停止并删除容器、网络
docker compose down -v 命名卷一起删除(数据也没了)

注意新老命令的区别 ⭐:

  • 新版docker compose(子命令,推荐)。
  • 旧版docker-compose(独立二进制,已被取代)。
    看到文档里两种写法,优先用 docker compose

⚠️ 常见错误

  1. down 不等于 stopstop 只是停,容器还在;down 会删除容器(-v 还会删卷)。
  2. docker exec 而不是 docker compose exec:Compose 容器名带了项目前缀,直接 docker exec db bash 常找不到容器,用 docker compose exec db bash 更稳妥。
  3. 改了 YAML 不重新 up:只改文件不 up -d,运行中的服务还是旧配置。up -d 会自动检测配置变化并重建。

5.4 项目与网络:Compose 怎么组织

项目名

Compose 把整组服务归为一个项目(project),默认用当前目录名作项目名,也可以指定:

docker compose -p myproject up -d     # 指定项目名

项目名直接影响容器名、网络名的前缀,如 myproject-db-1、网络 myproject_default。一套目录一套项目,互不干扰。

默认网络

Compose 会为项目自动创建一个自定义网络(默认模式就是自定义 bridge),项目内所有服务都接入它。这正是第 3 讲说的"自定义网络 + 容器名互访"——所以 web 能直接用 db 这个名字连数据库(见示例里的 DB_HOST: db)。

连接外部网络

默认网络只有本项目的服务能用。需要和项目之外的容器互通时,把网络声明为外部网络并接入:

networks:
  shared:
    external: true        # 外部网络,由 docker network create shared 预先创建
services:
  app:
    image: myapp:1.0
    networks:
      - shared

external: true 告诉 Compose "这个网络已经存在,不要创建"。共享网络常用于让多个独立项目访问同一个服务(比如共用一套 Redis、Nginx 网关)。

服务配置的多种写法

同一个服务既能 image 直接拉,也能 build 现构建:

services:
  api:
    build: ./backend         # 用本地 Dockerfile 构建
    image: myapi:1.0         # 构建产物命名(可选)

build 指向含 Dockerfile 的目录,image 给构建产物起名,方便之后直接分发。只写 image 不写 build,则纯用仓库镜像,不本地构建。

⚠️ 常见错误

  1. 跨项目访问想用服务名:不同 Compose 项目默认网络不同,直接 curl http://db 连不通;要么放同一网络,要么用外部网络(external: true)。
  2. 以为 down 会删卷down 只删容器和网络,命名卷默认保留(数据安全),只有显式 -v 才删。
  3. 多个服务占同一宿主端口ports 里写 "8080:80""8080:81" 会冲突,起第二个时报端口被占用。

📌 双语术语表(本讲)

中文 English 记忆点
编排 orchestration 管理多个容器
服务 service Compose 里的一个容器定义
项目 project 一组服务的集合
依赖关系 depends_on 控制启动顺序
环境变量文件 env_file 从文件注入配置
重启策略 restart 崩溃后自动拉起
命名卷 named volume 顶层声明后复用
外部网络 external network 跨项目共享
变量替换 variable substitution ${VAR:-default}
配置校验 compose config 检查最终配置
构建服务 build 本地 Dockerfile 构建

⭐ 本讲考点清单

  1. Compose 用 YAML 描述多容器应用,一条 up 拉起整组
  2. services.服务名 + image / build 定义服务
  3. 常用字段:ports / volumes / environment / env_file / depends_on / restart
  4. depends_on 只保证启动顺序,不保证服务就绪
  5. 命名卷需在顶层 volumes: 声明
  6. docker compose up -d / down / ps / logs -f / exec
  7. down 默认不删命名卷,down -v 才删
  8. 新版 docker compose vs 旧版 docker-compose
  9. 项目默认一个自定义网络,服务间用服务名互访
  10. YAML 缩进用空格,端口/卷写成字符串
  11. ${VAR:-默认值} 变量替换;docker compose config 校验配置
  12. build 指定 Dockerfile 目录,image 给产物命名或直接用仓库镜像
  13. external: true 接入外部预建网络,实现跨项目互通
  14. MySQL 镜像的 /docker-entrypoint-initdb.d 挂载目录:首次建库自动执行 .sql
  15. 完整应用骨架 = 对外服务 + 数据库 + 持久卷 + 健康检查