Docker Compose v2 完全实战指南:从单容器到多服务编排 | 2026最新最佳实践
Docker Compose 已经从一个简单的单机多容器编排工具,演进为覆盖开发、测试、生产全链路的容器管理解决方案。2026 年的今天,Compose v2 已完全取代旧版 docker-compose,新的 Compose Watch 功能甚至能实现代码变更的自动热重载。

本文将带你从零开始,系统掌握 Docker Compose v2 的完整知识体系,包括:
- Compose 文件结构与核心配置项详解
- 多服务编排实战(Nginx + Node.js + MySQL + Redis)
- 健康检查与服务依赖管理
- 数据卷与网络配置
- 环境变量与 Secrets 安全管理
- Compose Watch 自动更新
- 生产环境部署最佳实践
- 常见问题排错指南
一、Docker Compose 快速入门
1.1 Compose 能做什么?
Docker Compose 让你通过 一个 YAML 文件 定义和运行由多个容器组成的应用。典型场景:
| 场景 | 说明 |
|---|---|
| Web 应用栈 | 前端 + API 服务 + 数据库 + 缓存 |
| 微服务开发 | 多个微服务本地一键启动 |
| CI/CD 测试环境 | 一键拉起测试依赖(数据库、消息队列) |
| 单机生产部署 | 小型应用的简单部署方案 |
1.2 三步骤流程
Compose 的工作流程可以概括为三步:
# Step 1: 定义各服务的 Dockerfile(可选,也可直接用现成镜像)FROM node:20-alpineWORKDIR /appCOPY . .RUN npm install --productionCMD ["node", "server.js"]# Step 2: 编写 docker-compose.yml,定义服务间关系services: web: build: . ports: - "3000:3000" redis: image: "redis:alpine"# Step 3: 一条命令启动整个应用docker compose up -d1.3 验证安装
# Compose v2 使用 docker compose(有空格),而非 docker-composedocker compose version# 预期输出: Docker Compose v2.27.x 或更高二、docker-compose.yml 核心结构
Compose 文件使用 YAML 格式,以下是完整的结构示意:
# 现代 Compose 不再需要 version 字段(Compose Specification)
# 定义服务services: service-name: image: nginx:alpine # 使用的镜像 build: ./path/to/dockerfile # 或从 Dockerfile 构建 container_name: my-nginx # 自定义容器名 ports: # 端口映射 (HOST:CONTAINER) - "80:80" - "443:443" environment: # 环境变量 - NODE_ENV=production - DB_HOST=db volumes: # 数据卷挂载 - ./data:/var/lib/mysql # bind mount - db-data:/var/lib/mysql # named volume networks: # 网络配置 - frontend depends_on: # 依赖关系 - db restart: unless-stopped # 重启策略 healthcheck: # 健康检查 test: ["CMD", "curl", "-f", "http://localhost"] interval: 30s timeout: 10s retries: 3 start_period: 40s
# 定义网络(services 之间通过网络名通信)networks: frontend: driver: bridge backend: driver: bridge
# 定义命名数据卷(持久化存储)volumes: db-data: driver: local
# 定义机密信息(密码、密钥等)secrets: db_password: file: ./secrets/db_password.txt三、实战项目:Web 应用全栈编排
让我们从零构建一个完整的 Web 应用栈,包含以下服务:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ Nginx │────▶│ Node.js │────▶│ MySQL ││ (反向代理) │ │ (API服务) │ │ (数据库) │└─────────────┘ └─────────────┘ └─────────────┘ ▲ │ ┌────────┴───────┐ │ Redis │ │ (缓存/会话) │ └─────────────────┘3.1 完整的 docker-compose.yml
services: # 1. Nginx 反向代理 nginx: image: nginx:1.27-alpine container_name: app-nginx ports: - "80:80" - "443:443" volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/ssl:/etc/nginx/ssl:ro - ./static:/var/www/static:ro - nginx-logs:/var/log/nginx depends_on: api: condition: service_healthy networks: - frontend restart: unless-stopped healthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] interval: 30s timeout: 5s retries: 3 start_period: 10s
# 2. Node.js API 服务 api: build: context: ./api dockerfile: Dockerfile args: NODE_VERSION: 20 container_name: app-api environment: - NODE_ENV=production - PORT=3000 - DB_HOST=mysql - DB_PORT=3306 - DB_USER=${DB_USER} - DB_PASSWORD=${DB_PASSWORD} - REDIS_HOST=redis - REDIS_PORT=6379 volumes: - ./api:/app:ro - /app/node_modules expose: - "3000" depends_on: mysql: condition: service_healthy redis: condition: service_healthy networks: - frontend - backend restart: unless-stopped healthcheck: test: ["CMD", "node", "-e", "fetch('http://localhost:3000/health').then(r => process.exit(r.ok ? 0 : 1))"] interval: 30s timeout: 10s retries: 3 start_period: 30s deploy: resources: limits: cpus: "1.0" memory: 512M reservations: cpus: "0.25" memory: 128M
# 3. MySQL 数据库 mysql: image: mysql:8.4 container_name: app-mysql environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: ${DB_NAME} MYSQL_USER: ${DB_USER} MYSQL_PASSWORD: ${DB_PASSWORD} volumes: - mysql-data:/var/lib/mysql - ./mysql/init:/docker-entrypoint-initdb.d:ro expose: - "3306" networks: - backend restart: unless-stopped command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --default-authentication-plugin=mysql_native_password healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u${DB_USER}", "-p${DB_PASSWORD}"] interval: 10s timeout: 5s retries: 10 start_period: 60s
# 4. Redis 缓存 redis: image: redis:7.2-alpine container_name: app-redis command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru volumes: - redis-data:/data expose: - "6379" networks: - backend restart: unless-stopped healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5
# 网络配置networks: frontend: driver: bridge ipam: config: - subnet: 172.20.1.0/24 backend: driver: bridge internal: true # 内部网络,不可访问外网 ipam: config: - subnet: 172.20.2.0/24
# 命名数据卷(持久化)volumes: mysql-data: driver: local redis-data: driver: local nginx-logs: driver: local3.2 配套的 .env 文件
# .env - 放在 docker-compose.yml 同目录# 数据库配置DB_NAME=myapp_prodDB_USER=myappDB_PASSWORD=ChangeThisSecurePassword2026!DB_ROOT_PASSWORD=SuperSecureRootPassword!
# 应用配置NODE_ENV=productionAPI_PORT=3000
# Compose 项目名(可选)COMPOSE_PROJECT_NAME=myapp四、健康检查(Health Check)深度解析
健康检查是确保服务正常运行的关键机制。Compose 通过 healthcheck 配置健康检查策略。
4.1 健康检查的三种形式
# 形式 1: CMD - 在容器内执行命令(最常用)healthcheck: test: ["CMD", "curl", "-f", "http://localhost"] interval: 30s # 检查间隔 timeout: 10s # 单次超时时间 retries: 3 # 失败重试次数 start_period: 40s # 启动宽限期(服务启动需要时间)
# 形式 2: CMD-SHELL - 使用 shell 执行healthcheck: test: ["CMD-SHELL", "curl -f http://localhost || exit 1"] # 或简写为字符串: # test: curl -f http://localhost || exit 1
# 形式 3: NONE - 禁用健康检查healthcheck: disable: true4.2 常见服务的健康检查模板
# Nginxhealthcheck: test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"] interval: 30s timeout: 5s retries: 3
# MySQLhealthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uuser", "-ppass"] interval: 10s timeout: 5s retries: 10 start_period: 60s
# PostgreSQLhealthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5
# Redishealthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5
# Node.js / Expresshealthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"] interval: 20s timeout: 5s retries: 3 start_period: 30s
# Elasticsearchhealthcheck: test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q 'green\\|yellow'"] interval: 30s timeout: 10s retries: 5 start_period: 120s4.3 depends_on 与健康检查结合
depends_on 配合健康检查的 condition 字段,能严格控制启动顺序:
services: api: depends_on: mysql: condition: service_healthy # 等 mysql 健康后再启动 api redis: condition: service_healthy rabbitmq: condition: service_started # 只要启动就行condition 可用值:
service_started— 只要容器启动(默认行为)service_healthy— 等健康检查通过service_completed_successfully— 等服务执行完成并退出(适用于初始化任务)
五、数据卷(Volumes)管理
5.1 三种挂载方式对比
| 方式 | 语法 | 适用场景 | 持久化 |
|---|---|---|---|
| Named Volume | db-data:/var/lib/mysql | 数据库、持久化数据 | ✅ Docker 管理 |
| Bind Mount | ./data:/var/lib/mysql | 开发时代码挂载、配置文件 | ✅ 主机目录 |
| tmpfs | type: tmpfs | 临时数据、高并发写入(会话、缓存) | ❌ 内存存储 |
5.2 长格式配置(更灵活)
services: app: image: myapp:latest volumes: # 命名卷 - 最简形式 - mydata:/data
# 命名卷 - 长格式 - type: volume source: mydata target: /data volume: nocopy: true
# Bind Mount - 长格式 - type: bind source: ./config target: /etc/config read_only: true
# tmpfs - 临时文件系统 - type: tmpfs target: /tmp/cache tmpfs: size: 100m
volumes: mydata:5.3 数据卷操作命令
# 查看所有卷docker volume ls
# 查看卷详情docker volume inspect myapp_mysql-data
# 删除未使用的卷(谨慎)docker volume prune
# 备份数据卷docker run --rm -v myapp_mysql-data:/data -v $(pwd):/backup \ alpine tar cvzf /backup/mysql-backup.tar.gz -C /data .
# 恢复数据卷docker run --rm -v myapp_mysql-data:/data -v $(pwd):/backup \ alpine tar xvzf /backup/mysql-backup.tar.gz -C /data六、网络配置详解
6.1 Compose 默认网络行为
Compose 会为每个项目创建一个默认网络,同一 docker-compose.yml 中的服务可以通过服务名互相访问:
services: api: image: node:20 # api 服务可以通过 mysql:3306 访问数据库 mysql: image: mysql:8.4 # mysql 服务名即它的 DNS 名称6.2 多网络隔离(推荐)
services: nginx: networks: - frontend # 可被外部访问
api: networks: - frontend # 与 nginx 通信 - backend # 与数据库通信
mysql: networks: - backend # 仅内部网络,外网不可达
redis: networks: - backend # 仅内部网络
networks: frontend: driver: bridge
backend: driver: bridge internal: true # 标记为内部网络6.3 端口映射说明
services: web: ports: - "3000" # 随机主机端口映射到 3000 - "3000:3000" # 主机 3000 → 容器 3000(所有IP) - "127.0.0.1:3000:3000" # 仅本机访问(推荐用于调试) - "8080:80/tcp" # 指定协议 - "6379:6379/udp" # UDP 端口七、环境变量与 Secrets 管理
7.1 环境变量的四种传递方式
services: app: image: myapp:latest
# 方式 1: 直接键值对 environment: NODE_ENV: production PORT: 3000
# 方式 2: 数组形式(支持 shell 变量替换) environment: - NODE_ENV=production - DB_HOST=mysql
# 方式 3: 从主机环境变量读取(不指定值) environment: - API_KEY # 从 docker compose up 执行时的环境读取
# 方式 4: 从 .env 文件 env_file: - .env - .env.production # 多个文件,后加载的覆盖先加载的7.2 变量替换优先级
Compose 会按以下优先级查找变量值:
1. docker compose 命令行参数 → 最高优先级2. shell 环境变量3. .env 文件4. Compose 文件中的默认值7.3 使用 Docker Secrets 管理敏感信息
services: mysql: image: mysql:8.4 environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password MYSQL_PASSWORD_FILE: /run/secrets/db_password secrets: - db_root_password - db_password
secrets: db_root_password: file: ./secrets/db_root_password.txt # 文件内容即密码 db_password: file: ./secrets/db_password.txt
# secrets/db_password.txt (单独文件,不要提交到 Git!)# ChangeThisSecurePassword2026!八、Compose Watch:开发时代码热重载
Compose v2.22+ 新增的 Watch 功能让开发体验大幅提升:
services: web: build: . ports: - "3000:3000" develop: watch: # 场景 1: 代码变更 → 同步到容器内(不重启) - path: ./src action: sync target: /app/src ignore: - node_modules
# 场景 2: 依赖变更 → 重建容器 - path: package.json action: rebuild
# 场景 3: 配置变更 → 重启容器 - path: .env action: sync+restart target: /app/.env使用命令:
# 启动开发模式,自动监听文件变化docker compose watch
# 或正常 up 后单独启用docker compose up -ddocker compose watch # 另起一个终端监听九、多文件覆盖:区分开发与生产
9.1 基础 + 覆盖模式
# docker-compose.yml — 基础配置(所有环境共用)# docker-compose.dev.yml — 开发环境覆盖# docker-compose.prod.yml — 生产环境覆盖
# 开发环境启动docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
# 生产环境启动docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d9.2 开发环境覆盖示例
services: api: build: context: ./api target: development # 使用多阶段构建的开发阶段 volumes: - ./api:/app # 挂载源码到容器 - /app/node_modules # 匿名卷保留 node_modules environment: NODE_ENV: development DEBUG: true command: npm run dev # 开发模式启动 ports: - "9229:9229" # Node.js 调试端口
mysql: ports: - "3306:3306" # 开发时暴露数据库端口
volumes: mysql-data: driver_opts: type: none device: ${PWD}/mysql-data # 开发时挂载到本地目录 o: bind9.3 生产环境覆盖示例
services: api: image: registry.example.com/myapp/api:${TAG:-latest} build: target: production environment: NODE_ENV: production restart: always deploy: replicas: 2 # 多实例部署 resources: limits: cpus: "1.0" memory: 512M
nginx: image: registry.example.com/myapp/nginx:${TAG:-latest} restart: always logging: driver: json-file options: max-size: "10m" max-file: "5"十、常用命令速查表
# 启动服务docker compose up -d # 后台启动docker compose up -d --build # 先构建镜像再启动docker compose up -d --force-recreate # 强制重建容器docker compose --env-file .env.prod up -d
# 停止服务docker compose stop # 停止但不删除docker compose down # 停止并删除容器和网络docker compose down -v # 同时删除数据卷(谨慎!)docker compose down --rmi all # 同时删除镜像
# 查看状态docker compose ps # 查看服务状态docker compose ps -a # 查看所有容器(含已停止)docker compose logs # 查看所有服务日志docker compose logs -f --tail 100 # 实时查看最后 100 行docker compose logs api # 查看指定服务日志
# 执行命令docker compose exec api sh # 进入 api 容器docker compose exec mysql mysql -uuser -p
# 构建与管理docker compose build # 构建所有需要构建的服务docker compose build --no-cache # 无缓存构建docker compose pull # 拉取最新镜像docker compose images # 查看使用的镜像
# 伸缩服务docker compose up -d --scale api=3 # 启动 3 个 api 实例
# 健康检查docker compose ps # STATUS 列显示 healthy/unhealthydocker inspect <container> # 查看详细健康状态十一、生产环境最佳实践
✅ 镜像管理
# 1. 使用固定版本标签,避免 :latestimage: nginx:1.27-alpine
# 2. 使用多阶段构建减小镜像体积FROM node:20-alpine AS builderWORKDIR /appCOPY package*.json .RUN npm ciCOPY . .RUN npm run build
FROM node:20-alpineWORKDIR /appCOPY --from=builder /app/dist ./distCOPY --from=builder /app/node_modules ./node_modulesCMD ["node", "dist/server.js"]✅ 安全加固
services: app: user: "1000:1000" # 不以 root 运行 read_only: true # 只读文件系统 tmpfs: # 需要写入的位置用 tmpfs - /tmp - /var/run security_opt: - no-new-privileges:true # 禁止提升权限 cap_drop: # 删除不需要的 Linux capability - ALL cap_add: - NET_BIND_SERVICE # 只保留需要的 capability✅ 日志管理
services: app: logging: driver: json-file options: max-size: "10m" # 单个日志文件最大 10MB max-file: "5" # 最多保留 5 个文件 compress: "true" # 压缩旧日志✅ 资源限制
services: api: deploy: resources: limits: cpus: "1.5" # 最多使用 1.5 个 CPU 核 memory: 512M # 最多使用 512MB 内存 pids: 100 # 最多创建 100 个进程 reservations: cpus: "0.25" # 预留 0.25 核 memory: 128M # 预留 128MB 内存✅ 重启策略
services: app: restart: unless-stopped # 除非手动停止,否则自动重启 # 其他选项: # restart: "no" # 从不重启 # restart: always # 任何情况都重启(包括手动停止后再启动) # restart: on-failure # 仅在非正常退出时重启十二、常见问题排错指南
问题 1: 服务无法连接到数据库
Error: getaddrinfo ENOTFOUND mysql原因:服务启动时数据库尚未就绪。
解决方案:
services: api: depends_on: mysql: condition: service_healthy # 等数据库健康后再启动 mysql: healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u${DB_USER}", "-p${DB_PASSWORD}"] interval: 10s retries: 10 start_period: 60s问题 2: 数据卷权限问题
Permission denied: cannot write to /var/lib/mysql原因:容器内用户 UID/GID 与主机不匹配。
解决方案:
services: mysql: user: "1001:1001" # 指定运行用户 # 或者在 Dockerfile 中设置 # RUN usermod -u 1001 mysql问题 3: 端口被占用
Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:80解决方案:检查占用进程或更改映射端口:
# Linux/macOS - 查找占用端口的进程lsof -i :80# 或netstat -tlnp | grep 80问题 4: 镜像拉取失败
failed to solve with frontend dockerfile.v0: failed to create LLB definition原因:网络问题或镜像源不可达。
解决方案:
# 配置国内镜像加速{ "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://hub-mirror.c.163.com" ]}问题 5: 容器启动后立即退出
# 查看退出容器的日志docker compose logs api
# 进入容器调试docker compose run --rm api sh常见原因:
CMD命令错误或路径不正确- 配置文件缺失导致应用崩溃
- 数据库连接失败后未重试(应用应包含重试逻辑)
十三、完整项目模板参考
最后,附上一个可直接复用的项目目录结构:
myapp/├── docker-compose.yml # 基础配置├── docker-compose.dev.yml # 开发环境覆盖├── docker-compose.prod.yml # 生产环境覆盖├── .env # 环境变量(不要提交到 Git)├── .env.example # 环境变量模板├── .gitignore│├── api/ # Node.js 服务│ ├── Dockerfile│ ├── package.json│ └── src/│├── nginx/ # 反向代理│ ├── Dockerfile│ ├── conf.d/│ │ └── default.conf│ └── ssl/│├── mysql/ # 数据库初始化│ └── init/│ └── 01-schema.sql│├── secrets/ # 敏感文件(不要提交到 Git)│ ├── db_password.txt│ └── db_root_password.txt│└── scripts/ # 部署脚本 ├── deploy.sh └── backup.sh总结:Docker Compose 虽然设计初衷是单机编排工具,但凭借简洁的 YAML 配置、强大的健康检查与多文件覆盖机制,完全能胜任中小应用从开发到生产的全流程管理。配合 Compose Watch 的热重载能力,2026 年的 Compose 已不再是「玩具级工具」,而是生产可用的容器编排方案。掌握本文内容,你就能从容应对绝大多数场景下的容器部署需求。