暗色模式

Docker Compose 实战:从多容器编排到生产级最佳实践

技术教程
2026-08-02
7
0

docker run 一次只能起一个容器,而真实的应用几乎都是「多容器协作」:前端、后端、数据库、缓存、消息队列……靠一条条 docker run 命令启动,命令会越来越长,忘记参数、端口冲突、启动顺序错乱是家常便饭。

Docker Compose 就是来解决这个问题的:用一个 YAML 文件声明整组服务,一条命令启动、停止、重建整个应用栈。本文从零到实战,讲清 Compose 的核心概念、完整案例和生产级最佳实践。

安装与版本:认准 Compose V2

Compose V1(docker-compose,带横杠的命令)已于 2023 年 7 月停止更新,请一律使用 V2:

# V2 正确用法:docker compose(空格分隔)
docker compose version

# 未安装时,Docker Desktop / Docker Engine 插件方式
# Linux 上安装插件:
# sudo apt install docker-compose-plugin

V2 用 Go 语言重写,性能更好、原生支持 BuildKit,容器命名自动带项目前缀(如 myapp-web-1)。V1 时代的 version 字段已弃用,新写的 compose 文件直接省略即可:

# 正确:无需 version 字段
services:
  web:
    image: nginx:alpine

配置文件默认叫 compose.yamlcompose.yml(兼容旧名 docker-compose.yml)。

核心配置逐项拆解

一个 compose 文件的骨架只有三块:services(必需)、volumesnetworks

services:
  web:
    image: nginx:alpine     # 直接用镜像
  app:
    build: ./app            # 或从 Dockerfile 构建
volumes:
  db_data:                  # 命名卷
networks:
  app_net:                  # 自定义网络

image 与 build

  • image: 拉取现成镜像
  • build: 指定构建上下文(context),可配合 dockerfiletarget 做多阶段构建
  • 两者可同时写:先构建,再用 image 给构建结果命名

ports 与 expose

ports:
  - "8080:80"              # 宿主机8080 → 容器80
  - "127.0.0.1:3306:3306"  # 仅本机可访问(数据库标配)
  - "8000-8010:8000-8010"  # 端口范围
expose:
  - "3306"                 # 仅容器间可见, 不映射到宿主机

生产原则:数据库、Redis 等内部服务的端口只绑定 127.0.0.1,或干脆不映射——容器间通过服务名直连,根本不需要宿主端口。

volumes:三种挂载方式

volumes:
  - db_data:/var/lib/mysql    # 命名卷: Docker 管理, 数据持久化, 数据库首选
  - ./src:/app                # 绑定挂载: 开发热更新神器
  - type: tmpfs
    target: /tmp              # 内存盘: 临时文件

environment 与 env_file

environment:
  - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:?请设置数据库密码}  # 必填校验
  - DEBUG=${DEBUG:-false}                                       # 默认值
env_file:
  - .env

敏感信息永远不要硬编码进 compose 文件并提交 Git——用 .env 文件(加入 .gitignore)或 secrets 管理。

depends_on 与 healthcheck:正确的启动顺序

depends_on 只保证启动顺序,不保证服务就绪——MySQL 容器起来了不代表能接受连接。正确姿势是配合健康检查:

services:
  db:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 30s    # 启动宽限期, 初始化慢的镜像要给够
  app:
    build: ./app
    depends_on:
      db:
        condition: service_healthy   # 真正等 db 就绪

restart 与资源限制

services:
  app:
    restart: unless-stopped   # 生产推荐: 崩溃自动拉起, 手动停止不拉起
    deploy:
      resources:
        limits:
          cpus: "1.0"         # 单容器最多 1 核
          memory: 512M        # 最多 512MB

logging:防止日志撑爆磁盘

services:
  app:
    logging:
      driver: json-file
      options:
        max-size: "10m"     # 单个日志文件最大 10MB
        max-file: "3"       # 最多保留 3 个

实战案例:Web + MySQL + Redis 完整应用栈

以最常见的「后端 API + MySQL + Redis」为例,一份文件管起整个应用:

services:
  api:
    build: ./api
    ports:
      - "8080:8080"
    environment:
      DB_HOST: mysql          # 服务名即 DNS, 自动解析
      DB_NAME: myapp
      DB_USER: myapp
      DB_PASSWORD: ${DB_PASSWORD:?必须设置 DB_PASSWORD}
      REDIS_HOST: redis
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped
    deploy:
      resources:
        limits:
          memory: 512M
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: myapp
      MYSQL_USER: myapp
      MYSQL_PASSWORD: ${DB_PASSWORD:?必须设置 DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?必须设置 MYSQL_ROOT_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql   # 命名卷持久化
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 10
      start_period: 30s
    restart: unless-stopped
    # 不映射端口到宿主机, 仅容器网络内可访问

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 10
    restart: unless-stopped

volumes:
  mysql_data:
  redis_data:

启动与日常操作:

# 首次启动: 构建 + 后台运行
docker compose up -d --build

# 查看状态和日志
docker compose ps
docker compose logs -f api

# 进容器调试
docker compose exec api bash

# 验证配置是否正确(不启动)
docker compose config

# 停止并清理(容器+网络; -v 会连数据卷一起删, 慎用)
docker compose down

# 更新代码后重建
docker compose up -d --build

启动后,api 容器里直接 mysql:3306redis:6379 就能连上数据库和缓存——这就是 Compose 自动网络的功劳(同一项目内服务名即 DNS,详见之前的 Docker 网络模式详解)。

常用命令速查

命令用途
docker compose up -d后台启动全部服务
docker compose up -d --build重新构建并启动
docker compose down停止并删除容器/网络
docker compose down -v同上并删除数据卷(数据会丢,慎用
docker compose ps查看服务状态
docker compose logs -f [服务名]实时查看日志
docker compose exec [服务名] bash进入容器执行命令
docker compose run --rm api pytest运行一次性任务
docker compose config校验并查看合并后的配置
docker compose pull拉取所有服务的最新镜像
docker compose up --scale web=3扩容(指定了 container_name 或固定端口时不可用)
docker compose watch开发模式热重载

高级技巧

Profiles:一份文件管多套环境

把可选服务(压测、监控)打上 profile 标签,默认不启动,需要时显式拉起:

services:
  app:
    image: myapp:latest
  grafana:
    image: grafana/grafana
    profiles: ["monitoring"]   # 默认不启动
docker compose --profile monitoring up -d   # app + grafana
docker compose up -d                        # 只有 app

多文件合并:开发/生产配置分离

# compose.yaml         基础配置
# compose.prod.yaml    生产覆盖(资源限制/日志轮转等)
docker compose -f compose.yaml -f compose.prod.yaml up -d

开发环境绑定挂载源码(热更新),生产环境用镜像内置代码——兼顾开发效率与运行安全。

Watch 模式:开发热重载(V2.22+)

services:
  app:
    image: node:20
    develop:
      watch:
        - path: ./src
          action: sync          # 同步文件不重启(配合框架热加载)
        - path: ./package.json
          action: rebuild       # 依赖变化才重建镜像
docker compose watch

Secrets:敏感信息更安全的存放方式

官方镜像支持 *_FILE 后缀环境变量从文件读密码(如 MYSQL_ROOT_PASSWORD_FILE),配合 compose 的 secrets 挂载到 /run/secrets/

services:
  mysql:
    image: mysql:8.0
    secrets:
      - mysql_root_password
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password

secrets:
  mysql_root_password:
    file: ./secrets/mysql_root_password.txt   # 该文件不入 Git

最佳实践清单

  1. 启动顺序用 healthcheck + condition: service_healthy,别裸用 depends_onstart_period 要给够(MySQL 建议 30s+)
  2. 敏感信息进 .env 或 secrets.env.gitignore,提供 .env.example 模板,用 ${VAR:?error} 快速失败
  3. 生产环境 restart: unless-stopped(区别于 always:手动 stop 后不会自动拉起)
  4. 数据库不映射宿主端口(或只绑 127.0.0.1),容器间走服务名
  5. 配日志轮转(max-size/max-file),否则日志能撑爆磁盘
  6. 配资源限制(deploy.resources),防止单个容器吃光主机
  7. 复杂项目拆网络(frontend/backend 分网),数据库只放后端网络,最小权限原则
  8. 定期 docker system prune -f 清理悬空镜像与构建缓存

常见坑

坑 1:app 连不上数据库,报 connection refused。 十有八九是 depends_on 没配 healthcheck——MySQL 容器起来了但还没初始化完。按上文配 condition: service_healthy 解决。

坑 2:端口冲突起不来。 服务间改用服务名通信(桥接网络),宿主机端口冲突时改映射即可,docker compose ps 能看到具体报错。

坑 3:down -v 把数据库数据删了。 卷是持久化的命根子,删除前确认;备份用命名卷的导出(docker run --rm -v <卷名>:/data -v $(pwd):/backup alpine tar czf /backup/db.tar.gz /data)。

坑 4:.env 改了没生效。 环境变量在 up 时注入,改完 .env 需要重新 docker compose up -d(V2 会重新读取;但已运行容器需 recreate)。

坑 5:日志把磁盘写满。 没配 logging 限制的容器日志无限增长,du -sh /var/lib/docker/containers 能确认,配好 max-size/max-file 一劳永逸。

总结

Compose 的价值在于:把「一组容器怎么跑」从记忆和命令历史变成一份可版本化、可分享的配置文件。对中小项目、开发测试环境和流量不大的生产服务,一份写好的 compose 文件配合 CI/CD 完全够用,不必事事上 Kubernetes。

记住核心原则:开发用绑定挂载、生产用镜像内置;敏感信息走 .env/secrets;restart: unless-stopped + healthcheck + 日志轮转 + 资源限制,这四件套配齐,你的 compose 应用栈就稳了。

参考资料

发表评论

暂无评论,快来抢沙发吧!