3699 字
18 分钟

Docker Compose v2 完全实战指南:从单容器到多服务编排 | 2026最新最佳实践

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

Docker Compose 架构示意

本文将带你从零开始,系统掌握 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-alpine
WORKDIR /app
COPY . .
RUN npm install --production
CMD ["node", "server.js"]
# Step 2: 编写 docker-compose.yml,定义服务间关系
services:
web:
build: .
ports:
- "3000:3000"
redis:
image: "redis:alpine"
Terminal window
# Step 3: 一条命令启动整个应用
docker compose up -d

1.3 验证安装#

Terminal window
# Compose v2 使用 docker compose(有空格),而非 docker-compose
docker 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: local

3.2 配套的 .env 文件#

Terminal window
# .env - 放在 docker-compose.yml 同目录
# 数据库配置
DB_NAME=myapp_prod
DB_USER=myapp
DB_PASSWORD=ChangeThisSecurePassword2026!
DB_ROOT_PASSWORD=SuperSecureRootPassword!
# 应用配置
NODE_ENV=production
API_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: true

4.2 常见服务的健康检查模板#

# Nginx
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 5s
retries: 3
# MySQL
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uuser", "-ppass"]
interval: 10s
timeout: 5s
retries: 10
start_period: 60s
# PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
# Redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Node.js / Express
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 20s
timeout: 5s
retries: 3
start_period: 30s
# Elasticsearch
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q 'green\\|yellow'"]
interval: 30s
timeout: 10s
retries: 5
start_period: 120s

4.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 Volumedb-data:/var/lib/mysql数据库、持久化数据✅ Docker 管理
Bind Mount./data:/var/lib/mysql开发时代码挂载、配置文件✅ 主机目录
tmpfstype: 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 数据卷操作命令#

Terminal window
# 查看所有卷
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

使用命令:

Terminal window
# 启动开发模式,自动监听文件变化
docker compose watch
# 或正常 up 后单独启用
docker compose up -d
docker compose watch # 另起一个终端监听

九、多文件覆盖:区分开发与生产#

9.1 基础 + 覆盖模式#

Terminal window
# 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 -d

9.2 开发环境覆盖示例#

docker-compose.dev.yml
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: bind

9.3 生产环境覆盖示例#

docker-compose.prod.yml
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"

十、常用命令速查表#

Terminal window
# 启动服务
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/unhealthy
docker inspect <container> # 查看详细健康状态

十一、生产环境最佳实践#

✅ 镜像管理#

# 1. 使用固定版本标签,避免 :latest
image: nginx:1.27-alpine
# 2. 使用多阶段构建减小镜像体积
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["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

解决方案:检查占用进程或更改映射端口:

Terminal window
# Linux/macOS - 查找占用端口的进程
lsof -i :80
# 或
netstat -tlnp | grep 80

问题 4: 镜像拉取失败#

failed to solve with frontend dockerfile.v0: failed to create LLB definition

原因:网络问题或镜像源不可达。

解决方案

/etc/docker/daemon.json
# 配置国内镜像加速
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
]
}

问题 5: 容器启动后立即退出#

Terminal window
# 查看退出容器的日志
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 已不再是「玩具级工具」,而是生产可用的容器编排方案。掌握本文内容,你就能从容应对绝大多数场景下的容器部署需求。

Docker Compose v2 完全实战指南:从单容器到多服务编排 | 2026最新最佳实践
https://971918.xyz/posts/docs/docker-compose-guide/
作者
九所长
发布于
2026-06-13
许可协议
CC BY-NC-SA 4.0