Docker Compose

真实项目几乎都是多容器组合:Web 应用 + 数据库 + 缓存 + 反向代理。一个个 docker run 写一长串参数太难维护,而且没法版本化。Docker Compose 用一个 YAML 文件描述所有服务,一条命令管理全部——这是本地开发的神器

为什么需要 Compose

先看没 Compose 时多痛苦:

# 为什么用 Compose?

# 没有Compose 时,跑一个 web+db+redis 要这样:

docker run -d --name db -v dbdata:/var/lib/postgresql/data \
  -e POSTGRES_USER=app -e POSTGRES_PASSWORD=secret postgres:16

docker run -d --name cache redis:7

docker run -d --name web -p 3000:3000 \
  -e DATABASE_URL=postgres://app:secret@db:5432/app \
  --network mynet myapp

# 问题:
# 1. 命令又长又难记,改个参数要重新敲一遍
# 2. 启动顺序靠人肉保证(先 db 再 web)
# 3. 团队协作没法版本化(同事不知道你跑了啥)
# 4. 一年后回来自己也忘了当时怎么跑的

# 有了 Compose:这些参数全部写进 docker-compose.yml
# 团队共享一个文件,任何人 clone 后:
docker compose up -d
# 完整环境就起来了。版本控制、协作、复现,一步到位

Compose 把这些参数全部写进 docker-compose.yml,团队共享、版本控制、一键复现。同事 clone 项目后不用配环境,一条 docker compose up -d 就能跑起来完整服务——这就是它最大的价值。

基础模板

一个典型 Web + DB + 缓存 的 docker-compose.yml 长这样:

# docker-compose.yml:用一个文件描述"多个容器怎么协同"
# 文件名固定 docker-compose.yml(或 .yaml),放在项目根目录

# 版本号(新版 Compose 已不需要,但写上兼容旧版)
version: "3.9"

services:
  # 第一个服务:Web 应用
  web:
    build: .                          # 从当前目录的 Dockerfile 构建
    ports:
      - "3000:3000"                   # 主机端口:容器端口
    environment:
      - DATABASE_URL=postgres://app:secret@db:5432/app
      - REDIS_URL=redis://cache:6379
      - NODE_ENV=production
    depends_on:
      - db                            # 等 db 启动后再启动 web
      - cache
    restart: unless-stopped           # 崩溃自动重启
    volumes:
      - ./src:/app/src                # 开发时挂载源码,改代码立即生效

  # 第二个服务:数据库
  db:
    image: postgres:16-alpine         # 直接用现成镜像
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
      POSTGRES_DB: app
    volumes:
      - dbdata:/var/lib/postgresql/data   # 数据持久化(命名卷)
    ports:
      - "5432:5432"

  # 第三个服务:缓存
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"

# 顶层声明命名卷(被 services 引用)
volumes:
  dbdata:

三个顶层块要分清:

depends_on 只控制启动顺序,不等服务"就绪"。比如 db 容器启动了但 MySQL 还在初始化,web 立刻连会失败。要等"就绪"得用 condition: service_healthy 配合 healthcheck(见进阶配置)。

全套命令

# 用 Compose 管理一整套服务

# 一键启动所有服务(-d 后台,首次会自动构建镜像)
docker compose up -d

# 看运行状态(类似 docker ps,但只看本项目)
docker compose ps

# 看所有服务日志(实时跟随)
docker compose logs -f
docker compose logs -f web              # 只看 web
docker compose logs --tail 100 web

# 在运行中的服务里执行命令
docker compose exec db psql -U app
docker compose exec web sh

# 重新构建镜像(改了 Dockerfile 后)
docker compose build
docker compose build --no-cache web      # 不用缓存构建 web

# 重启某个服务
docker compose restart web

# 停止所有服务(容器还在,可被 start 唤醒)
docker compose stop

# 停止并删除容器、网络(数据卷默认保留)
docker compose down

# 连数据卷一起删(清空数据库数据,慎用!)
docker compose down -v

新版命令是 docker compose(空格,集成到主命令,V2 用 Go 重写,更快);老版本是 docker-compose(带横杠,Python 写的独立脚本)。两者功能基本一致,新项目用前者。docker compose down 默认保留数据卷,这是设计如此——避免误删数据库数据;确定要清空加 -v

进阶配置

真实项目的 docker-compose.yml 通常更复杂,涵盖各种场景:

# docker-compose.yml 进阶配置

services:
  web:
    build:
      context: ./backend               # 构建上下文目录
      dockerfile: Dockerfile.prod      # 指定 Dockerfile
      args:                            # 构建参数(ARG)
        BUILD_NUMBER: ${BUILD_NUMBER}
    image: myapp:1.0                   # 构建后镜像名(可推到仓库)
    container_name: myapp-web          # 固定容器名
    hostname: web                      # 容器内 hostname
    environment:
      - NODE_ENV=production
      - LOG_LEVEL=$${LOG_LEVEL}        # 引用主机环境变量(两个$$转义)
    env_file:
      - .env                           # 批量从文件读环境变量
    ports:
      - "3000:3000"
    volumes:
      - ./src:/app/src                 # 绑定挂载
      - logs:/app/logs                 # 命名卷
    networks:
      - frontend
      - backend
    depends_on:
      db:
        condition: service_healthy     # 等 db 健康检查通过才启动
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
    restart: unless-stopped
    deploy:                            # 仅 Swarm 模式生效
      replicas: 3
      resources:
        limits:
          memory: 512M

networks:
  frontend:
  backend:
    internal: true                     # 内部网络,不能出网

volumes:
  logs:
  dbdata:
    driver: local

几个高频技巧:

多环境:override 文件

开发和生产配置通常不同(开发要热重载,生产要压缩)。Compose 支持文件叠加,优雅解决:

# 多环境:用 override 文件覆盖配置

# 默认 Compose 会自动合并两个文件:
#   docker-compose.yml       基础配置(生产/通用)
#   docker-compose.override.yml  开发覆盖(自动加载,不用 -f 指定)

# docker-compose.yml(基础)
services:
  web:
    build: .
    environment:
      - NODE_ENV=production

# docker-compose.override.yml(开发覆盖)
services:
  web:
    command: npm run dev               # 覆盖启动命令为热重载
    environment:
      - NODE_ENV=development           # 覆盖环境变量
    volumes:
      - ./src:/app/src                 # 加挂载,改代码即时生效
      - /app/node_modules              # 防止主机 node_modules 覆盖容器内的

# 显式指定多个文件(任意命名)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
docker compose -f docker-compose.yml -f docker-compose.test.yml up -d

# 区分环境配置:
#   docker-compose.yml             基础
#   docker-compose.override.yml    本地开发(自动加载)
#   docker-compose.prod.yml        生产(显式 -f 指定)
#   docker-compose.test.yml        CI 测试

关键认知:Compose 默认会自动加载 docker-compose.yml + docker-compose.override.yml(后者覆盖前者)。所以约定是:基础文件写通用配置、override 写本地开发覆盖.gitignoredocker-compose.override.yml 排除,每人自定义;生产配置另起 docker-compose.prod.yml,用 -f 显式指定。

实战:3 分钟搭一套 WordPress

把上面学的全用上,体会 Compose 的爽:

# 实战:3 分钟搭一套博客系统(WordPress)

# docker-compose.yml
version: "3.9"
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wp
      MYSQL_PASSWORD: wppass
    volumes:
      - dbdata:/var/lib/mysql

  wordpress:
    image: wordpress:6-php8.2-apache
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_USER: wp
      WORDPRESS_DB_PASSWORD: wppass
      WORDPRESS_DB_NAME: wordpress
    depends_on:
      - db

volumes:
  dbdata:

# 一条命令启动整套
docker compose up -d
# 浏览器访问 http://localhost:8080 进入 WordPress 安装向导
# 完事 docker compose down 清理

对比一下:不用 Compose,你要敲两条巨长的 docker run、记一堆参数、配网络。现在一个 yml 文件 + 一条命令搞定,而且能版本控制、能复现。这就是 Compose 的魔力。

常见坑

Compose 是本地开发不可替代的工具,但生产环境(尤其是多机集群)通常改用 Kubernetes——后者解决"跨主机编排、滚动发布、自动扩缩容"等更复杂的问题。学完 Compose 再看 K8s,概念上会顺畅很多。

← 上一篇 Docker 网络

下一篇 Docker 镜像仓库

✈️💬