为什么需要 Docker Compose?
当你的应用只有单个容器时,用 docker run 命令就够了。但真实项目通常是多组件的:
- 一个 Nginx 前端
- 一个 Node.js / Python / Java 后端 API
- 一个 PostgreSQL / MySQL 数据库
- 一个 Redis 缓存
手动用 docker run 管理这些容器会变成噩梦:端口映射、数据卷挂载、网络互联、环境变量、启动顺序……每条命令几十个参数,还无法版本化。
Docker Compose 用一个声明式的 YAML 文件描述整个多容器应用栈,一条命令即可启动、停止、重建所有服务。它是本地开发、CI 流水线、小型生产部署的标配工具。
安装与版本
Docker Desktop(Windows / macOS)已内置 Compose。Linux 需单独安装 docker-compose-plugin,用 docker compose(V2,推荐)而非 docker-compose(V1,已停止维护)。
# 验证安装
docker compose version
# Docker Compose version v2.27.0
核心概念
服务(Services)
每个 service 定义一个容器。最简单的例子:
# docker-compose.yml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./html:/usr/share/nginx/html
网络(Networks)
Compose 默认会为每个项目创建一个网络,同一 docker-compose.yml 中的服务可用服务名互相访问,无需知道对方 IP:
services:
web:
image: nginx:alpine
depends_on:
- api
api:
image: node:20-alpine
# web 容器内可访问 http://api:3000
也可自定义网络以做隔离:
services:
web:
networks:
- frontend
api:
networks:
- frontend
- backend
db:
networks:
- backend
networks:
frontend:
backend:
此时 web 无法直接访问 db,因为它们不在同一网络。
数据卷(Volumes)
容器是临时的,删除后数据消失。持久化数据用 volumes:
services:
db:
image: postgres:16
volumes:
# 命名卷:由 Docker 管理,删除容器不丢数据
- pgdata:/var/lib/postgresql/data
# 绑定挂载:直接映射宿主机路径,适合开发时热加载代码
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
pgdata:
两种挂载区别:
| 类型 | 语法 | 用途 |
|---|---|---|
| 命名卷 | name:/path/in/container |
持久化数据库等运行时数据 |
| 绑定挂载 | ./host/path:/container/path |
开发时同步源代码、配置文件 |
环境变量(Environment)
三种方式配置环境变量:
services:
api:
image: myapp:latest
environment:
- NODE_ENV=production
- LOG_LEVEL=info
# 或用 map 形式
env_file:
- .env
# 文件中:KEY=value,每行一个
推荐用 .env 文件,且加入 .gitignore 避免泄漏密钥。Compose 也会自动读取项目根目录的 .env 文件用于变量插值:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD} # 从 .env 读取
.env 文件:
DB_PASSWORD=supersecret
实战:典型 Web 应用栈
下面是一个生产级 Web 应用(Nginx + Node API + PostgreSQL + Redis)的完整配置:
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- api
restart: unless-stopped
api:
build:
context: ./backend
dockerfile: Dockerfile
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://app:${DB_PASSWORD}@db:5432/app
- REDIS_URL=redis://redis:6379
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redisdata:/data
restart: unless-stopped
volumes:
pgdata:
redisdata:
常用命令速查
# 启动所有服务(后台)
docker compose up -d
# 查看运行状态
docker compose ps
# 查看日志(实时跟踪)
docker compose logs -f api
# 重新构建并启动(代码变更后)
docker compose up -d --build
# 仅重启某个服务
docker compose restart api
# 进入容器
docker compose exec api sh
# 停止并删除容器、网络(保留数据卷)
docker compose down
# 停止并删除包括数据卷(慎用!数据库数据会丢失)
docker compose down -v
# 查看资源占用
docker compose stats
常见模式与最佳实践
1. 开发环境热加载
挂载源码并启用文件监听:
services:
api:
build: ./backend
volumes:
- ./backend:/app
- /app/node_modules # 匿名卷避免宿主覆盖
command: npm run dev
environment:
- CHOKIDAR_USEPOLLING=true # 解决 Docker Desktop for Windows 文件监听问题
2. 多环境配置
用多个 compose 文件叠加:
# 基础 + 开发覆盖
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# 基础 + 生产覆盖
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
docker-compose.dev.yml 示例(覆盖命令、暴露调试端口):
services:
api:
command: npm run dev
ports:
- "9229:9229" # Node.js 调试端口
environment:
- NODE_ENV=development
3. 健康检查与启动顺序
depends_on 默认只保证容器启动,不保证服务就绪。用 healthcheck + condition:
services:
api:
depends_on:
db:
condition: service_healthy
这避免了"API 启动时数据库还没准备好"的经典坑。
4. 资源限制
生产环境限制 CPU 和内存,防止单个容器拖垮宿主机:
services:
api:
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
memory: 256M
5. 日志轮转
避免日志撑爆磁盘:
services:
api:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
常见坑
.env 文件不被读取
- 必须放在执行
docker compose命令的目录,而非 compose 文件所在目录 - 变量插值
${VAR}在 YAML 解析时替换,与容器内environment注入是两回事 - 调试用
docker compose config查看最终合并后的完整配置
绑定挂载权限问题(Linux)
宿主机文件挂载到容器后,容器内进程 UID 与宿主机不一致会导致写权限错误。两种解法:
# 方案一:构建镜像时切换到非 root 用户并匹配 UID
services:
api:
user: "${UID}:${GID}"
# 方案二:挂载后用 init 容器 chown
端口占用
docker compose up 报 port is already allocated,但 docker ps 看不到占用容器:
# 检查宿主机端口占用
# Windows
netstat -ano | findstr :8080
# Linux/Mac
lsof -i :8080
可能是其他程序占用,改用其他端口即可。
Windows 下文件挂载性能差
Docker Desktop for Windows 使用 WSL2 后端时,挂载 Windows 磁盘(/c/...)到容器有较大 IO 损耗。建议把项目放在 WSL2 文件系统内(\\wsl$\Ubuntu\home\...),性能可提升 5-10 倍。
Compose vs Swarm vs Kubernetes
| 场景 | 工具 |
|---|---|
| 单机开发 / 小型部署 | Docker Compose ✅ |
| 多机集群 / 高可用 | Docker Swarm(轻量)或 Kubernetes(生态完善) |
| 企业级生产编排 | Kubernetes |
Compose 的 docker-compose.yml 可直接被 Docker Swarm 部署(docker stack deploy),但 K8s 需要转换为 Kubernetes manifests(可用 Kompose 工具辅助)。
总结
Docker Compose 把多容器应用从命令行脚本升级为可版本化、可复现的声明式配置。掌握 services、volumes、networks、environment 四大核心概念,配合健康检查、多环境覆盖、资源限制等模式,足以覆盖绝大多数本地开发和小型生产场景。当规模扩大到多节点时,再平滑迁移到 Kubernetes。
| 要点 | 说明 |
|---|---|
| services | 定义每个容器,包括镜像、端口、依赖 |
| volumes | 命名卷持久化数据,绑定挂载同步代码 |
| networks | 默认按服务名互通,自定义网络做隔离 |
| environment | .env 文件 + 变量插值是最佳实践 |
| depends_on + healthcheck | 保证启动顺序与服务就绪 |
| 多文件覆盖 | dev.yml / prod.yml 分环境配置 |