Docker Compose 生产级部署实战指南
目标读者:有 Docker 基础的开发者
难度:进阶
字数:约 4000 字
适用场景:中小规模微服务、API 后端、Web 应用的生产环境部署
一、Docker Compose 基础结构回顾
Compose 的核心是一个 docker-compose.yml 文件,由四大顶层元素构成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| version: "3.9"
services: app: image: myapp:latest ports: - "8080:8080"
networks: backend: driver: overlay
volumes: data:
configs: app_config: file: ./config/app.yml
|
| 元素 |
作用 |
生产要点 |
| services |
定义容器服务 |
镜像锁定 tag,避免用 latest |
| networks |
定义容器间通信拓扑 |
使用 overlay 驱动(Swarm)或自定义 bridge |
| volumes |
持久化存储声明 |
优先命名卷,避免匿名卷 |
| configs |
注入配置文件(Swarm only) |
配合 secrets 管理敏感数据 |
⚠️ 生产第一原则:所有镜像必须指定精确版本(如 postgres:15.4-alpine),杜绝 latest 标签。
二、多环境管理:Compose 文件拆分策略
2.1 三层拆分法
1 2 3
| docker-compose.yml # 公共基础配置 docker-compose.override.yml # 开发环境(默认自动加载) docker-compose.prod.yml # 生产环境覆盖
|
生产启动命令:
1
| docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
2.2 最佳实践示例
docker-compose.yml(公共部分):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| version: "3.9"
services: app: image: myapp:${APP_VERSION:-latest} restart: unless-stopped networks: - frontend - backend
redis: image: redis:7.2-alpine restart: unless-stopped networks: - backend
networks: frontend: backend:
|
docker-compose.prod.yml(生产覆盖):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| version: "3.9"
services: app: deploy: replicas: 3 resources: limits: cpus: "1.0" memory: 512M reservations: cpus: "0.25" memory: 128M healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s
redis: volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 3
volumes: redis_data:
|
💡 搭配 .env 文件区分环境变量,见第四章。
三、健康检查与依赖启动顺序控制
3.1 healthcheck 配置详解
1 2 3 4 5 6 7 8 9
| services: postgres: image: postgres:15.4-alpine healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 start_period: 30s
|
3.2 depends_on 的三种模式
1 2 3 4 5 6 7 8 9
| services: app: depends_on: postgres: condition: service_healthy redis: condition: service_started migrator: condition: service_completed_successfully
|
⚠️ 常见坑:depends_on 只控制启动顺序,不保证容器内部服务已就绪。必须配合 healthcheck 才能实现真正的依赖等待。
3.3 初始化容器的模式
1 2 3 4 5 6 7 8 9
| services: db_migrate: image: myapp:${APP_VERSION} command: ["./wait-for-it.sh", "postgres:5432", "--", "npm", "run", "migrate"] depends_on: postgres: condition: service_healthy profiles: - init
|
手动运行:
1
| docker compose --profile init run --rm db_migrate
|
四、环境变量管理最佳实践
4.1 分层变量体系
1 2 3
| .env # 公共变量(提交到 git 的模板) .env.prod # 生产敏感变量(不提交 git) .env.local # 开发环境覆盖(不提交 git)
|
4.2 .env 文件示例
1 2 3 4 5 6 7 8 9 10 11
| APP_VERSION=1.5.2 APP_ENV=production LOG_LEVEL=info TZ=Asia/Shanghai
DB_PASSWORD=*** REDIS_PASSWORD=*** JWT_SECRET=your-2...here API_KEY=***
|
4.3 在 Compose 中引用
1 2 3 4 5 6 7 8 9 10 11
| services: app: image: myapp:${APP_VERSION} environment: - NODE_ENV=${APP_ENV} - LOG_LEVEL=${LOG_LEVEL} - DB_PASSWORD=*** - REDIS_PASSWORD=*** - TZ=${TZ} env_file: - .env
|
4.4 禁止的行为
| ❌ 禁止做法 |
✅ 正确做法 |
| 在 Compose 中硬编码密码 |
使用 ${VAR} 引用 .env |
| 将 .env.prod 提交到 git |
添加到 .gitignore |
| 同一个 .env 文件跨所有环境 |
分环境维护 |
| 在镜像中嵌入 secrets |
使用 Docker secrets 或 env_file 注入 |
💡 高阶技巧:使用 docker compose config 验证变量替换是否正确:
1
| docker compose -f docker-compose.yml -f docker-compose.prod.yml config
|
五、数据持久化:三种方式的选择
5.1 命名卷(Named Volumes)— 首选
1 2 3 4 5 6 7
| services: postgres: volumes: - pg_data:/var/lib/postgresql/data
volumes: pg_data:
|
适用场景:数据库、消息队列、任何需要 Docker 管理生命周期的数据。
优点:自动备份友好,docker volume 命令可管理,跨节点迁移(Swarm)支持。
5.2 绑定挂载(Bind Mounts)— 谨慎使用
1 2 3 4 5 6 7
| services: nginx: volumes: - type: bind source: /host/path/to/static target: /usr/share/nginx/html read_only: true
|
适用场景:开发热重载、日志收集、Nginx 静态文件。
风险:依赖宿主机路径、权限问题、不可跨 Swarm 节点。
5.3 tmpfs — 内存级临时数据
1 2 3 4 5 6 7 8 9 10
| services: app: tmpfs: - /tmp:noexec,nosuid,size=128M volumes: - type: tmpfs target: /var/run/app tmpfs: size_mb: 64
|
适用场景:临时缓存、session 存储、敏感数据(不希望落盘)。
5.4 决策树
1 2 3 4 5 6 7
| 数据需要持久化? ├─ 是 → 需要跨节点共享? │ ├─ 是 → 网络存储(NFS/Ceph → 命名卷 + volume driver) │ └─ 否 → 单节点 → 命名卷 ✅ └─ 不需要 → 数据可丢失? ├─ 是 → tmpfs └─ 否 → ... 那你需要持久化!
|
⚠️ 常见生产事故:忘记声明 volumes: 顶层键,导致 Docker 创建匿名卷,docker compose down -v 时被一并删除。
六、网络模式与服务发现
6.1 自定义网络
1 2 3 4 5 6 7 8 9 10
| networks: frontend: driver: bridge ipam: config: - subnet: 172.20.0.0/16 gateway: 172.20.0.1 backend: driver: bridge internal: true
|
6.2 服务发现与隔离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| services: nginx: networks: - frontend - backend
app: networks: - backend - internal
postgres: networks: - internal
|
规则:
- 各服务按最小权限原则只加入必需的网络
- 同网络内,服务名等同 DNS 主机名(
app, redis, postgres)
internal: true 的网络没有对外网关,提升安全性
6.3 固定 IP(需要时)
1 2 3 4 5
| services: nginx: networks: frontend: ipv4_address: 172.20.0.10
|
💡 生产中最常用的布局:一层反向代理网络(frontend)+ 一层业务网络(backend)+ 一层数据层网络(internal)。
七、日志配置
7.1 驱动选择对比
| 驱动 |
适用场景 |
存储位置 |
json-file |
单机、简单调试 |
/var/lib/docker/containers/<id>/ |
local |
性能敏感场景 |
Docker 管理(二进制格式) |
syslog |
集中式日志已有设施 |
syslog 服务器 |
fluentd |
需要日志转发到 Elastic 等 |
Fluentd 收集器 |
gelf |
Graylog 集成 |
Graylog 服务器 |
loki |
Grafana 全家桶 |
Loki 实例 |
awslogs |
AWS 环境 |
CloudWatch |
7.2 日志轮转配置(json-file)
1 2 3 4 5 6 7 8 9
| services: app: logging: driver: json-file options: max-size: "10m" max-file: "3" compress: "true" tag: "{{.Name}}/{{.ID}}"
|
7.3 集中式日志(Fluentd 示例)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| services: app: logging: driver: fluentd options: fluentd-address: "localhost:24224" fluentd-async-connect: "true" tag: "myapp.{{.Name}}"
fluentd: image: fluent/fluentd:v1.16 volumes: - ./fluentd.conf:/fluentd/etc/fluent.conf ports: - "24224:24224" networks: - logging
|
全局默认配置(在 Compose 文件顶层设置):
1 2 3 4 5
| logging: driver: json-file options: max-size: "10m" max-file: "3"
|
⚠️ 常见坑:日志驱动 none 会导致 docker logs 无法查看日志,生产排障时非常痛苦。除非磁盘空间极度紧张,否则不要用 none。
八、安全最佳实践
8.1 非 root 运行
1 2 3 4 5 6
| services: app: image: node:18-alpine user: "1000:1000"
|
Dockerfile 侧配合:
1 2 3
| RUN addgroup -g 1000 -S appgroup && \ adduser -u 1000 -S appuser -G appgroup USER appuser
|
8.2 Secrets 管理
1 2 3 4 5 6 7 8 9 10 11 12 13
| secrets: db_password: file: ./secrets/db_password.txt jwt_secret: external: true
services: app: secrets: - db_password - jwt_secret
|
单机替代方案(利用 tmpfs + env_file):
1 2
| docker compose run --rm -e DB_PASSWORD=*** secrets/db_password.txt) app
|
8.3 资源限制
1 2 3 4 5 6 7 8 9 10 11 12 13
| services: app: deploy: resources: limits: cpus: "1.5" memory: 512M pids: 100 reservations: cpus: "0.5" memory: 256M oom_kill_disable: false restart: unless-stopped
|
8.4 只读根文件系统
1 2 3 4 5 6
| services: app: read_only: true tmpfs: - /tmp - /var/run
|
⚠️ 配合 read_only: true 时,应用需要写入的目录(如 /tmp、缓存目录)必须用 tmpfs 或 volume 显式声明。
8.5 安全清单
九、部署流程
9.1 单机生产部署
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| mkdir -p /opt/myapp/{config,data,logs,secrets} cd /opt/myapp
git clone https://github.com/org/myapp-deploy.git .
echo "prod-db-password" > secrets/db_password.txt chmod 600 secrets/db_password.txt
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
docker compose ps docker compose logs --tail=50
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps --scale app=2
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --no-deps --scale app=3
|
9.2 Docker Swarm 部署
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| version: "3.9"
services: app: image: myapp:${APP_VERSION} deploy: mode: replicated replicas: 3 update_config: parallelism: 1 delay: 10s order: start-first rollback_config: parallelism: 1 delay: 5s order: stop-first restart_policy: condition: any delay: 5s
|
部署命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| docker swarm init --advertise-addr 192.168.1.100
docker stack deploy -c docker-compose.yml -c docker-compose.prod.yml myapp
docker stack services myapp
docker service update --image myapp:1.6.0 myapp_app
docker service rollback myapp_app
|
9.3 零宕机更新策略
1 2 3 4 5 6 7
| deploy: update_config: parallelism: 2 delay: 10s order: start-first failure_action: rollback monitor: 30s
|
十、常见坑与调试技巧
10.1 经典问题速查表
| 问题 |
现象 |
解决方案 |
| 容器启动后立即退出 |
docker logs 无输出 |
检查 CMD 是否前台运行;检查 entrypoint 权限 |
| 服务间无法解析主机名 |
连接被拒绝 |
检查是否在同一网络;使用 docker compose exec app ping redis |
| Volume 权限拒绝 |
PostgreSQL 启动失败 |
容器用户 UID 与 volume 所有者不匹配;使用 user: "999:999" |
.env 变量不生效 |
docker compose config 显示空值 |
检查 .env 文件位置(必须与 compose 同目录) |
| 端口冲突 |
port is already allocated |
lsof -i :8080 找到占用进程;或外层使用反向代理 |
| 日志撑爆磁盘 |
docker system df 看到巨量日志 |
配置 max-size/max-file;定期 docker system prune |
10.2 调试三板斧
1 2 3 4 5 6 7 8 9 10
| docker compose -f docker-compose.yml -f docker-compose.prod.yml config
docker compose logs -f --tail=100 --timestamps app
docker compose exec app sh
docker run --rm -it --network myapp_backend nicolaka/netshoot
|
10.3 实用命令快速参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| docker system prune -a --volumes
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
docker compose exec app env | grep -E "DB_|REDIS_"
docker compose -f docker-compose.yml -f docker-compose.prod.yml config > /tmp/resolved.yml
docker volume inspect myapp_pg_data --format '{{.Mountpoint}}'
|
附录:生产级 Compose 模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| version: "3.9"
x-logging: &default-logging driver: json-file options: max-size: "10m" max-file: "3" compress: "true"
x-deploy: &default-deploy resources: limits: cpus: "1.0" memory: 512M reservations: cpus: "0.25" memory: 128M restart_policy: condition: any delay: 5s
services: app: image: myapp:${APP_VERSION:?APP_VERSION is required} user: "1000:1000" read_only: true tmpfs: - /tmp:size=64M environment: - NODE_ENV=${APP_ENV:-production} - LOG_LEVEL=${LOG_LEVEL:-info} - TZ=Asia/Shanghai env_file: - .env depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - backend logging: *default-logging deploy: *default-deploy
postgres: image: postgres:15.4-alpine user: "999:999" volumes: - pg_data:/var/lib/postgresql/data environment: POSTGRES_DB: ${DB_NAME} POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"] interval: 10s timeout: 5s retries: 5 start_period: 30s networks: - internal logging: *default-logging
redis: image: redis:7.2-alpine user: "999:999" volumes: - redis_data:/data command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"] healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 3 networks: - internal logging: *default-logging
networks: backend: driver: bridge internal: driver: bridge internal: true
volumes: pg_data: redis_data:
|
总结
| 维度 |
核心要点 |
| 镜像 |
锁定版本标签,减少攻击面,优先 -alpine |
| 配置 |
拆分环境,.env + profiles 组合管理 |
| 健康 |
依赖服务必须配合 healthcheck,start_period 设置合理 |
| 存储 |
命名卷优先,绑定挂载有限使用,tmpfs 用于临时数据 |
| 网络 |
多网络分层隔离,internal: true 保护数据层 |
| 日志 |
配置轮转,生产环境接入集中式日志系统 |
| 安全 |
非 root 用户、只读文件系统、资源限制、secrets 文件注入 |
| 部署 |
Swarm stack 或 docker compose,配合更新策略实现零宕机 |
上一篇:《Docker 容器化入门到实践》
作者:CaoZH · 发布于 2026-06-18