04. Docker Compose 完全指南
4.1 Docker Compose 概述
4.1.1 为什么需要 Docker Compose?
痛点场景:
bash
# 传统方式部署一个 Web 应用需要:
docker network create app-network
docker run -d --name db --network app-network \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=myapp \
mysql:8.0
docker run -d --name redis --network app-network \
redis:alpine
docker run -d --name api --network app-network \
-e DB_HOST=db \
-e REDIS_HOST=redis \
-p 8080:8080 \
my-api:latest
docker run -d --name web --network app-network \
-p 80:80 \
my-web:latest
# 需要记住多个命令,难以管理和复制!
使用 Docker Compose:
yaml
# docker-compose.yml
version: '3.8'
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
redis:
image: redis:alpine
api:
image: my-api:latest
environment:
DB_HOST: db
REDIS_HOST: redis
ports:
- "8080:8080"
web:
image: my-web:latest
ports:
- "80:80"
bash
# 一条命令启动所有服务
docker-compose up -d
4.1.2 Docker Compose 是什么?
Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。
4.1.3 安装 Docker Compose
Docker Desktop(Windows/Mac):
- 已内置 Docker Compose,无需单独安装
Linux 安装:
bash
# 方法 1:使用 Docker 官方插件(推荐)
sudo apt-get update
sudo apt-get install docker-compose-plugin
# 验证安装
docker compose version
# 方法 2:下载二进制文件
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 验证
docker-compose --version
注意:
- 新版本使用
docker compose(空格) - 旧版本使用
docker-compose(连字符) - 本文档使用新版语法
4.2 docker-compose.yml 文件结构
4.2.1 基本结构
yaml
version: '3.8' # Compose 文件版本
services: # 定义服务
service-name:
# 服务配置
image: image-name
ports:
- "host:container"
networks: # 定义网络(可选)
network-name:
driver: bridge
volumes: # 定义数据卷(可选)
volume-name:
driver: local
configs: # 配置文件(可选)
config-name:
file: ./config.conf
secrets: # 密钥(可选)
secret-name:
file: ./secret.txt
4.2.2 版本对照表
| 版本 | Docker Engine | 主要特性 |
|---|---|---|
| 3.8 | 19.03.0+ | 推荐版本 |
| 3.7 | 18.06.0+ | 稳定版本 |
| 3.0-3.6 | 1.13.0+ | 旧版本 |
| 2.x | 1.10.0+ | 已废弃 |
推荐使用 3.8 版本。
4.3 Service 配置详解
4.3.1 image - 指定镜像
yaml
services:
web:
# 使用 Docker Hub 镜像
image: nginx:alpine
app:
# 使用私有仓库镜像
image: myregistry.com/myapp:v1.0
db:
# 使用特定摘要
image: mysql@sha256:abc123...
4.3.2 build - 构建镜像
yaml
services:
app:
# 简单形式:指定 Dockerfile 所在目录
build: .
api:
# 完整形式
build:
context: ./api # 构建上下文目录
dockerfile: Dockerfile.prod # Dockerfile 文件名
args: # 构建参数
VERSION: 1.0
NODE_ENV: production
target: production # 多阶段构建目标
cache_from: # 缓存来源
- myapp:cache
示例:
yaml
services:
web:
build:
context: .
dockerfile: Dockerfile
args:
- NODE_VERSION=18
image: my-web:latest # 构建后的镜像名
ports:
- "3000:3000"
4.3.3 container_name - 容器名称
yaml
services:
web:
image: nginx
container_name: my-nginx # 自定义容器名
# 不指定则自动生成:<项目名>_<服务名>_<序号>
# 例如:myproject_web_1
4.3.4 ports - 端口映射
yaml
services:
web:
image: nginx
ports:
# 简写形式:"主机端口:容器端口"
- "8080:80"
- "8443:443"
# 只指定容器端口(主机端口随机)
- "80"
# 长格式(更明确)
- target: 80 # 容器端口
published: 8080 # 主机端口
protocol: tcp # 协议
mode: host # 模式
# 绑定到特定 IP
- "127.0.0.1:8080:80"
4.3.5 environment - 环境变量
yaml
services:
db:
image: mysql:8.0
environment:
# Map 格式
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: myapp
MYSQL_USER: user
MYSQL_PASSWORD: password
app:
image: myapp
environment:
# Array 格式
- NODE_ENV=production
- API_KEY=abc123
- DEBUG=false
使用 .env 文件:
.env 文件:
env
# 数据库配置
DB_PASSWORD=secret123
DB_NAME=myapp
# 应用配置
APP_PORT=8080
docker-compose.yml:
yaml
services:
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
app:
image: myapp
environment:
DB_HOST: db
DB_PASSWORD: ${DB_PASSWORD}
ports:
- "${APP_PORT}:8080"
4.3.6 volumes - 数据卷
yaml
services:
db:
image: mysql:8.0
volumes:
# 命名卷
- mysql-data:/var/lib/mysql
# 绑定挂载(相对路径)
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
# 绑定挂载(绝对路径)
- /host/path:/container/path
# 只读挂载
- ./config:/etc/config:ro
# 长格式
- type: volume
source: mysql-data
target: /var/lib/mysql
# tmpfs
- type: tmpfs
target: /tmp
tmpfs:
size: 100M
# 定义命名卷
volumes:
mysql-data:
driver: local
4.3.7 networks - 网络配置
yaml
services:
web:
image: nginx
networks:
# 连接到单个网络
- frontend
api:
image: myapi
networks:
# 连接到多个网络
- frontend
- backend
db:
image: mysql
networks:
backend:
# 指定 IP 地址
ipv4_address: 172.20.0.10
# 定义网络
networks:
frontend:
driver: bridge
backend:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
4.3.8 depends_on - 依赖关系
yaml
services:
web:
image: nginx
depends_on:
- api # 先启动 api,再启动 web
api:
image: myapi
depends_on:
- db
- redis
db:
image: mysql
redis:
image: redis
# 启动顺序:db, redis → api → web
# 注意:depends_on 只保证启动顺序,不保证服务就绪
高级用法(等待服务就绪):
yaml
services:
web:
image: nginx
depends_on:
api:
condition: service_healthy # 等待健康检查通过
api:
image: myapi
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 10s
timeout: 5s
retries: 3
4.3.9 restart - 重启策略
yaml
services:
web:
image: nginx
restart: always # 总是重启
api:
image: myapi
restart: unless-stopped # 除非手动停止,否则总是重启
worker:
image: worker
restart: on-failure # 失败时重启
batch:
image: batch-job
restart: "no" # 不重启(默认)
重启策略说明:
no:不自动重启always:总是重启(即使手动停止)on-failure:退出码非 0 时重启unless-stopped:总是重启,除非手动停止
4.3.10 command - 覆盖默认命令
yaml
services:
app:
image: node:18
command: npm start # 简单形式
worker:
image: python:3.11
command: ["python", "worker.py"] # 数组形式
db:
image: mysql:8.0
command: --default-authentication-plugin=mysql_native_password
4.3.11 entrypoint - 覆盖入口点
yaml
services:
app:
image: myapp
entrypoint: /app/entrypoint.sh
worker:
image: myapp
entrypoint: ["python", "manage.py"]
command: ["celery", "worker"] # 作为 entrypoint 的参数
4.3.12 healthcheck - 健康检查
yaml
services:
api:
image: myapi
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s # 检查间隔
timeout: 10s # 超时时间
retries: 3 # 重试次数
start_period: 40s # 启动等待时间
# 禁用健康检查
web:
image: nginx
healthcheck:
disable: true
4.3.13 labels - 标签
yaml
services:
web:
image: nginx
labels:
com.example.description: "Web Server"
com.example.version: "1.0"
# 数组形式
api:
image: myapi
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.example.com`)"
4.3.14 其他常用配置
yaml
services:
app:
image: myapp
# 工作目录
working_dir: /app
# 用户
user: "1000:1000"
# 主机名
hostname: myapp-host
# DNS
dns:
- 8.8.8.8
- 8.8.4.4
# DNS 搜索域
dns_search:
- example.com
# 额外主机映射
extra_hosts:
- "somehost:192.168.1.100"
- "otherhost:192.168.1.101"
# 资源限制
deploy:
resources:
limits:
cpus: '0.50'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
# 日志配置
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
4.4 Docker Compose 命令
4.4.1 基本命令
bash
# 启动所有服务(后台运行)
docker compose up -d
# 启动所有服务(前台运行,查看日志)
docker compose up
# 启动特定服务
docker compose up -d web api
# 停止所有服务
docker compose down
# 停止服务但保留容器
docker compose stop
# 启动已停止的服务
docker compose start
# 重启服务
docker compose restart
# 重启特定服务
docker compose restart web
4.4.2 查看状态
bash
# 查看服务状态
docker compose ps
# 查看所有容器(包括已停止的)
docker compose ps -a
# 查看服务日志
docker compose logs
# 实时查看日志
docker compose logs -f
# 查看特定服务的日志
docker compose logs -f web
# 查看最后 100 行日志
docker compose logs --tail=100 web
# 显示时间戳
docker compose logs -t
4.4.3 构建和拉取
bash
# 构建服务
docker compose build
# 构建时不使用缓存
docker compose build --no-cache
# 构建特定服务
docker compose build web
# 拉取服务镜像
docker compose pull
# 拉取特定服务镜像
docker compose pull db redis
4.4.4 执行命令
bash
# 在运行的容器中执行命令
docker compose exec web sh
docker compose exec db mysql -uroot -p
# 在新容器中执行一次性命令
docker compose run web npm install
docker compose run api python manage.py migrate
# 不启动依赖服务
docker compose run --no-deps web npm test
# 删除容器后自动清理
docker compose run --rm worker python script.py
4.4.5 扩展和伸缩
bash
# 扩展服务实例数量
docker compose up -d --scale web=3 --scale api=2
# 查看扩展后的容器
docker compose ps
4.4.6 清理资源
bash
# 停止并删除容器、网络
docker compose down
# 同时删除数据卷(危险!)
docker compose down -v
# 同时删除镜像
docker compose down --rmi all
# 删除孤立容器
docker compose down --remove-orphans
4.4.7 其他实用命令
bash
# 验证 compose 文件
docker compose config
# 查看服务的配置
docker compose config --services
# 暂停服务
docker compose pause web
# 恢复服务
docker compose unpause web
# 查看容器进程
docker compose top
# 查看服务端口
docker compose port web 80
4.5 实战案例
案例 1:LAMP 架构(Apache + PHP + MySQL)
项目结构:
lamp-app/
├── docker-compose.yml
├── www/
│ └── index.php
├── mysql/
│ └── init.sql
└── apache/
└── apache.conf
docker-compose.yml:
yaml
version: '3.8'
services:
db:
image: mysql:8.0
container_name: lamp-mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: myapp
MYSQL_USER: appuser
MYSQL_PASSWORD: apppass
volumes:
- mysql-data:/var/lib/mysql
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- lamp-network
restart: unless-stopped
phpmyadmin:
image: phpmyadmin:latest
container_name: lamp-phpmyadmin
environment:
PMA_HOST: db
PMA_PORT: 3306
ports:
- "8081:80"
depends_on:
- db
networks:
- lamp-network
restart: unless-stopped
web:
image: php:8.2-apache
container_name: lamp-web
ports:
- "8080:80"
volumes:
- ./www:/var/www/html
- ./apache/apache.conf:/etc/apache2/sites-available/000-default.conf
depends_on:
- db
networks:
- lamp-network
restart: unless-stopped
volumes:
mysql-data:
networks:
lamp-network:
driver: bridge
启动:
bash
docker compose up -d
# 访问:http://localhost:8080
# phpMyAdmin:http://localhost:8081
案例 2:MEAN 架构(MongoDB + Express + Angular + Node.js)
docker-compose.yml:
yaml
version: '3.8'
services:
mongodb:
image: mongo:6
container_name: mean-mongo
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: secret
MONGO_INITDB_DATABASE: meandb
volumes:
- mongo-data:/data/db
ports:
- "27017:27017"
networks:
- mean-network
restart: unless-stopped
backend:
build: ./backend
container_name: mean-api
environment:
NODE_ENV: development
MONGO_URI: mongodb://admin:secret@mongodb:27017/meandb?authSource=admin
PORT: 3000
ports:
- "3000:3000"
volumes:
- ./backend:/app
- /app/node_modules
depends_on:
- mongodb
networks:
- mean-network
restart: unless-stopped
command: npm run dev
frontend:
build: ./frontend
container_name: mean-frontend
ports:
- "4200:4200"
volumes:
- ./frontend:/app
- /app/node_modules
depends_on:
- backend
networks:
- mean-network
restart: unless-stopped
command: npm start
volumes:
mongo-data:
networks:
mean-network:
driver: bridge
案例 3:微服务架构(带 NGINX 反向代理)
docker-compose.yml:
yaml
version: '3.8'
services:
# NGINX 反向代理
nginx:
image: nginx:alpine
container_name: microservices-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
depends_on:
- user-service
- product-service
- order-service
networks:
- frontend
restart: unless-stopped
# 用户服务
user-service:
build: ./services/user
container_name: user-service
environment:
DB_HOST: postgres
REDIS_HOST: redis
depends_on:
- postgres
- redis
networks:
- frontend
- backend
restart: unless-stopped
# 产品服务
product-service:
build: ./services/product
container_name: product-service
environment:
DB_HOST: postgres
REDIS_HOST: redis
depends_on:
- postgres
- redis
networks:
- frontend
- backend
restart: unless-stopped
# 订单服务
order-service:
build: ./services/order
container_name: order-service
environment:
DB_HOST: postgres
REDIS_HOST: redis
RABBITMQ_HOST: rabbitmq
depends_on:
- postgres
- redis
- rabbitmq
networks:
- frontend
- backend
restart: unless-stopped
# PostgreSQL 数据库
postgres:
image: postgres:15-alpine
container_name: microservices-postgres
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_DB: microservices
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- backend
restart: unless-stopped
# Redis 缓存
redis:
image: redis:7-alpine
container_name: microservices-redis
networks:
- backend
restart: unless-stopped
# RabbitMQ 消息队列
rabbitmq:
image: rabbitmq:3-management-alpine
container_name: microservices-rabbitmq
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: secret
ports:
- "5672:5672"
- "15672:15672" # 管理界面
networks:
- backend
restart: unless-stopped
volumes:
postgres-data:
networks:
frontend:
driver: bridge
backend:
driver: bridge
案例 4:WordPress 完整部署
docker-compose.yml:
yaml
version: '3.8'
services:
db:
image: mysql:8.0
container_name: wordpress-db
volumes:
- db-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
networks:
- wordpress-network
restart: unless-stopped
wordpress:
image: wordpress:latest
container_name: wordpress-app
depends_on:
- db
ports:
- "8000:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
- wordpress-data:/var/www/html
networks:
- wordpress-network
restart: unless-stopped
# 可选:phpMyAdmin
phpmyadmin:
image: phpmyadmin:latest
container_name: wordpress-phpmyadmin
depends_on:
- db
ports:
- "8081:80"
environment:
PMA_HOST: db
MYSQL_ROOT_PASSWORD: somewordpress
networks:
- wordpress-network
restart: unless-stopped
volumes:
db-data:
wordpress-data:
networks:
wordpress-network:
driver: bridge
启动和配置:
bash
# 启动服务
docker compose up -d
# 访问 WordPress 安装页面
# http://localhost:8000
# 访问 phpMyAdmin
# http://localhost:8081
4.6 高级技巧
4.6.1 多环境配置
基础配置(docker-compose.yml):
yaml
version: '3.8'
services:
app:
image: myapp:latest
environment:
NODE_ENV: ${NODE_ENV}
开发环境覆盖(docker-compose.override.yml):
yaml
version: '3.8'
services:
app:
build: .
volumes:
- ./src:/app/src
environment:
DEBUG: "true"
command: npm run dev
生产环境覆盖(docker-compose.prod.yml):
yaml
version: '3.8'
services:
app:
image: myapp:prod
restart: always
deploy:
replicas: 3
resources:
limits:
cpus: '0.5'
memory: 512M
使用方法:
bash
# 开发环境(自动加载 override)
docker compose up -d
# 生产环境
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
4.6.2 扩展和继承
yaml
# 定义可复用的配置
x-logging: &default-logging
driver: json-file
options:
max-size: "10m"
max-file: "3"
x-healthcheck: &default-healthcheck
interval: 30s
timeout: 10s
retries: 3
services:
web:
image: nginx
logging: *default-logging
healthcheck:
<<: *default-healthcheck
test: ["CMD", "curl", "-f", "http://localhost"]
api:
image: myapi
logging: *default-logging
healthcheck:
<<: *default-healthcheck
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
4.6.3 使用 profiles 管理服务组
yaml
version: '3.8'
services:
# 核心服务(总是启动)
db:
image: postgres:15
api:
image: myapi
depends_on:
- db
# 开发工具(仅开发时启动)
adminer:
image: adminer
profiles: ["dev"]
ports:
- "8080:8080"
# 测试服务(仅测试时启动)
test-runner:
image: test-runner
profiles: ["test"]
command: npm test
使用:
bash
# 只启动核心服务
docker compose up -d
# 启动核心服务 + 开发工具
docker compose --profile dev up -d
# 启动核心服务 + 测试服务
docker compose --profile test up -d
4.7 关键命令
bash
docker compose up -d # 启动服务
docker compose down # 停止服务
docker compose ps # 查看状态
docker compose logs -f # 查看日志
docker compose exec # 执行命令
docker compose build # 构建镜像
docker compose restart # 重启服务