跳到主要内容
预计阅读 26 分钟

一键指挥千军万马 —— Docker Compose多容器编排实战

一个容器是独奏,多个容器是交响乐。Docker Compose就是那个站在台上的指挥家,让所有乐手按照总谱协调演奏。

📋 开篇自测:你已经知道多少?

  1. 你知道docker-compose.yml文件的基本结构吗?
  2. docker compose updocker compose up -d 有什么区别?
  3. 如何让Compose中的某个服务等待另一个服务启动后再运行?

一、手动编排的痛苦

回顾一下上一章的实战例子:我们启动了三个容器(Nginx + Node.js + Redis),需要敲一堆命令:

docker network create webapp-net
docker volume create pgdata
docker run -d --name redis --network webapp-net redis:7.2
docker run -d --name nodeapp --network webapp-net -e REDIS_HOST=redis my-node-app
docker run -d --name nginx --network webapp-net -p 80:80 -v ./nginx.conf:/etc/nginx/conf.d/default.conf:ro nginx

这才三个容器就已经这么长了。如果你的项目有十几个微服务,加上数据库、缓存、消息队列、监控系统…手动管理简直是噩梦。而且每次要重启整套环境,还得记住正确的启动顺序。

更糟糕的是,这些命令散落在你的脑子里(或者某个笔记里),新同事接手项目时完全不知道怎么把环境跑起来。

Docker Compose就是为了终结这种混乱而生的。它让你把所有容器的配置写在一个YAML文件里,然后一条命令就能启动、停止、重建整套环境。


二、docker-compose.yml:你的编排剧本

让我们把刚才手动敲的那堆命令,翻译成一个 docker-compose.yml 文件:

# docker-compose.yml

services:
  # Nginx反向代理
  nginx:
    image: nginx:1.28-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - nodeapp
    networks:
      - webapp-net

  # Node.js应用
  nodeapp:
    build: ./app
    environment:
      - REDIS_HOST=redis
      - NODE_ENV=production
    depends_on:
      - redis
    networks:
      - webapp-net

  # Redis缓存
  redis:
    image: redis:7.2-alpine
    volumes:
      - redis-data:/data
    networks:
      - webapp-net

networks:
  webapp-net:
    driver: bridge

volumes:
  redis-data:

然后只需一条命令:

docker compose up -d

所有容器按照依赖关系自动创建网络、创建Volume、拉取/构建镜像、启动容器。想要停掉整套环境?也是一条命令:

docker compose down

从一堆零散的docker run命令,到一个结构化的配置文件加一条命令——这就是Docker Compose带来的改变。

YAML文件结构剖析

一个docker-compose.yml文件主要包含以下几个顶级配置块:

services:最核心的部分,定义了所有需要运行的容器。每个服务(service)对应一个容器。

networks:定义网络。如果不写,Compose会自动创建一个默认网络,所有服务都连在上面。

volumes:定义命名Volume。

让我们逐个看看 services 里的常用配置项。

🤔 想一想 把所有配置集中到一个YAML文件里,除了操作方便之外,还有什么好处?(提示:想想版本控制、文档化、团队协作)


三、服务配置详解

image vs build

services:
  # 直接使用现成镜像
  redis:
    image: redis:7.2-alpine

  # 从Dockerfile构建镜像
  webapp:
    build: ./webapp
    # 或者更详细的构建配置
    # build:
    #   context: ./webapp
    #   dockerfile: Dockerfile.prod
    #   args:
    #     NODE_ENV: production

image 指定使用哪个已有镜像,build 指定从本地Dockerfile构建。两者也可以同时使用——先构建,然后给构建出来的镜像打上指定的名称标签。

端口映射

services:
  web:
    image: nginx
    ports:
      - "80:80"         # 宿主机80 → 容器80
      - "443:443"       # 宿主机443 → 容器443
      - "127.0.0.1:8080:80"  # 只绑定本地

环境变量

services:
  app:
    image: my-app
    environment:
      - NODE_ENV=production
      - DB_HOST=postgres
      - DB_PORT=5432
    # 或者用键值对写法
    # environment:
    #   NODE_ENV: production
    #   DB_HOST: postgres

  # 从文件加载环境变量
  app-from-file:
    image: my-app
    env_file:
      - .env
      - .env.production

使用 env_file 加载环境变量文件是更好的做法,特别是当你有很多环境变量或者不想把敏感信息写在YAML文件里时。.env 文件应该被添加到 .gitignore 中。

数据卷

services:
  postgres:
    image: postgres:16
    volumes:
      # 命名Volume(持久化数据库数据)
      - pgdata:/var/lib/postgresql/data
      # Bind Mount(挂载初始化脚本)
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro

volumes:
  pgdata:

依赖关系

services:
  webapp:
    build: ./app
    depends_on:
      - postgres
      - redis

  postgres:
    image: postgres:16

  redis:
    image: redis:7.2

depends_on 控制启动顺序:先启动postgres和redis,再启动webapp。但要注意一个坑:depends_on只保证容器启动的顺序,不保证服务就绪。也就是说,postgres容器可能已经启动了,但PostgreSQL服务还没准备好接受连接。

更可靠的做法是使用健康检查:

services:
  webapp:
    build: ./app
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 3s
      retries: 5

这样webapp会等到postgres通过健康检查后才启动。

重启策略

services:
  app:
    image: my-app
    restart: unless-stopped
    # 可选值:
    # "no"           - 默认,不自动重启
    # always         - 总是重启
    # on-failure     - 非正常退出时重启
    # unless-stopped - 除非手动停止,否则一直重启

生产环境推荐使用 unless-stoppedalways,确保服务在崩溃后自动恢复。

资源限制

services:
  app:
    image: my-app
    deploy:
      resources:
        limits:
          cpus: "0.5"      # 最多使用0.5个CPU核心
          memory: 512M      # 最多使用512MB内存
        reservations:
          cpus: "0.25"     # 预留0.25个CPU核心
          memory: 256M      # 预留256MB内存

四、Compose常用命令

# 启动所有服务(前台运行,可看日志)
docker compose up

# 后台启动
docker compose up -d

# 只启动特定服务
docker compose up -d postgres redis

# 停止并删除容器、网络
docker compose down

# 停止并删除容器、网络、Volume(慎用!会删除数据)
docker compose down -v

# 查看服务状态
docker compose ps

# 查看日志
docker compose logs

# 实时追踪特定服务的日志
docker compose logs -f webapp

# 在服务容器中执行命令
docker compose exec postgres psql -U postgres

# 重新构建镜像
docker compose build

# 重新构建并启动(代码改了之后常用)
docker compose up -d --build

# 扩展服务实例数
docker compose up -d --scale webapp=3

# 停止服务(不删除容器)
docker compose stop

# 启动已停止的服务
docker compose start

# 重启服务
docker compose restart

⚠️ 常见误区

  • 误区一:“docker compose down会删除数据”。默认不会。down 只删除容器和网络。只有加上 -v 参数才会删除Volume。但确实要小心,别手滑加了 -v
  • 误区二:“depends_on能保证服务完全就绪”。不能。它只控制启动顺序。要等服务就绪,需要配合healthcheck。
  • 误区三:“修改了docker-compose.yml后需要down再up”。不需要。直接 docker compose up -d 就行,Compose会智能地只重建有变化的服务。

📌 阶段小结:到这里,你已经掌握了Docker Compose的核心知识——YAML文件结构、常用服务配置项(image/build、ports、volumes、environment、depends_on等)以及日常操作命令。接下来我们进入实战环节,用Compose搭建一套真实的博客系统。


五、实战:搭建一个完整的博客系统

让我们用Docker Compose搭建一个包含WordPress、MySQL和Nginx的完整博客系统:

# docker-compose.yml

services:
  nginx:
    image: nginx:1.28-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      - wordpress
    restart: unless-stopped
    networks:
      - frontend

  wordpress:
    image: wordpress:6-fpm-alpine
    environment:
      WORDPRESS_DB_HOST: mysql
      WORDPRESS_DB_USER: wpuser
      WORDPRESS_DB_PASSWORD: wppass123
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp-content:/var/www/html
    depends_on:
      mysql:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - frontend
      - backend

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass123
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wpuser
      MYSQL_PASSWORD: wppass123
    volumes:
      - mysql-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  wp-content:
  mysql-data:

注意这个架构的网络设计:

  • Nginx和WordPress在frontend网络中
  • WordPress和MySQL在backend网络中
  • Nginx不能直接访问MySQL——提供了额外的安全层

对应的Nginx配置文件 nginx.conf

server {
    listen 80;
    server_name localhost;
    root /var/www/html;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        fastcgi_pass wordpress:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

一条命令启动整套博客系统:

docker compose up -d

等几十秒钟让所有服务就绪后,打开浏览器访问 http://localhost,你应该能看到WordPress的安装向导。

整个博客系统——Web服务器、应用、数据库——从无到有,就是一个YAML文件加一条命令的事。


六、多环境配置管理

实际项目中,开发环境和生产环境的配置往往不同。Docker Compose支持使用多个配置文件来管理这种差异:

# docker-compose.yml(基础配置)
services:
  webapp:
    build: ./app
    environment:
      - DB_HOST=postgres

  postgres:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:
# docker-compose.override.yml(开发环境,自动加载)
services:
  webapp:
    build:
      context: ./app
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./app/src:/app/src  # 热重载
    environment:
      - NODE_ENV=development
      - DEBUG=true

  postgres:
    ports:
      - "5432:5432"  # 开发时暴露数据库端口,方便用GUI工具连接
    environment:
      - POSTGRES_PASSWORD=devpass
# docker-compose.prod.yml(生产环境,需要手动指定)
services:
  webapp:
    build:
      context: ./app
      dockerfile: Dockerfile.prod
    environment:
      - NODE_ENV=production
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M

  postgres:
    environment:
      - POSTGRES_PASSWORD=${DB_PASSWORD}  # 从环境变量读取
    restart: unless-stopped
# 开发环境(自动合并 docker-compose.yml + docker-compose.override.yml)
docker compose up -d

# 生产环境(手动指定配置文件)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

这种分层配置的方式既保持了公共配置的统一,又允许不同环境有各自的差异。

🤔 想一想 你的项目有开发、测试、预发布、生产四个环境。你会怎么组织docker-compose配置文件?是每个环境一个完整的文件,还是一个基础文件加多个覆盖文件?


七、Compose实用技巧

查看合并后的完整配置

# 查看最终生效的配置(合并了所有配置文件后的结果)
docker compose config

只重建有变化的服务

# 智能重建:只有相关的Dockerfile或依赖变了的服务才会重建
docker compose up -d --build

一次性任务

# 使用run执行一次性命令(不影响正在运行的服务)
docker compose run --rm webapp npm run migrate
docker compose run --rm webapp python manage.py createsuperuser

查看服务的资源使用

docker compose top      # 查看各服务的进程
docker compose stats    # 查看资源使用情况(CPU、内存等)

.env文件与变量替换

docker-compose.yml 同级目录下创建 .env 文件:

# .env
POSTGRES_VERSION=16
APP_PORT=3000
DB_PASSWORD=supersecret
# docker-compose.yml中可以引用这些变量
services:
  postgres:
    image: postgres:${POSTGRES_VERSION}
  webapp:
    ports:
      - "${APP_PORT}:3000"

📝 掌握度自测

  1. 写出一个最简单的docker-compose.yml,包含一个Nginx服务和一个Redis服务,它们在同一个自定义网络中。

  2. docker compose downdocker compose down -v 的区别是什么?什么时候用前者,什么时候用后者?

  3. depends_on 的局限性是什么?如何确保一个服务在另一个服务”真正就绪”后才启动?

  4. 如何在不停掉整套环境的情况下,只重建和重启其中一个服务?

  5. 解释docker-compose.yml中 volumes 顶级配置块和service内 volumes 配置的区别。

💡 自我评估

  • 全部答对:你已经可以用Docker Compose管理复杂的多容器应用了。后面的实战篇会进一步提升你的技能。
  • 答对3-4题:很好的基础。建议把本章的WordPress博客系统实际搭建一遍,感受Compose的便利。
  • 答对1-2题:Docker Compose的配置项确实比较多。先记住最核心的几个:services、image/build、ports、volumes、environment、depends_on。其他的用到再查文档。

购买课程解锁全部内容

告别「在我电脑上能跑」:Docker 容器化实战

¥29.90