Docker Compose 生产级部署实战指南

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 # 连续失败几次标记为 unhealthy
start_period: 30s # 启动宽限期,不计入 retries

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
# .env
APP_VERSION=1.5.2
APP_ENV=production
LOG_LEVEL=info
TZ=Asia/Shanghai

# .env.prod(不提交 git)
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 管理,存储在 /var/lib/docker/volumes/

适用场景:数据库、消息队列、任何需要 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 # 可访问 API

app:
networks:
- backend
- internal
# 不接入 frontend,客户端无法直接访问

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" # 单个日志文件最大 10MB
max-file: "3" # 最多保留 3 个文件
compress: "true" # 轮转后 gzip 压缩
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" # 指定非 root 用户
# 或者使用下面方式(更灵活):
# user: "${UID:-1000}:${GID:-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
# 生产 Swarm 模式
secrets:
db_password:
file: ./secrets/db_password.txt # 仅 swarm 模式支持
jwt_secret:
external: true # 从外部已创建的 secret 获取

services:
app:
secrets:
- db_password
- jwt_secret
# 容器内路径:/run/secrets/db_password

单机替代方案(利用 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" # 最多 1.5 核
memory: 512M # 最多 512MB
pids: 100 # 最多 100 个进程
reservations:
cpus: "0.5" # 保证 0.5 核
memory: 256M # 保证 256MB
oom_kill_disable: false # OOM 时允许系统杀掉容器
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 安全清单

  • 镜像使用 -alpine-slim 变体减少攻击面
  • 启用 read_only: true
  • 指定 user: 非 root
  • Secrets 不写入环境变量(通过文件注入)
  • 日志中不包含敏感信息
  • 网络采用最小权限隔离
  • 配置内存/CPU/进程数限制

九、部署流程

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
# 1. 准备环境
mkdir -p /opt/myapp/{config,data,logs,secrets}
cd /opt/myapp

# 2. 拉取配置文件
git clone https://github.com/org/myapp-deploy.git .

# 3. 创建 secrets
echo "prod-db-password" > secrets/db_password.txt
chmod 600 secrets/db_password.txt

# 4. 预拉取镜像
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull

# 5. 启动服务
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 6. 验证
docker compose ps
docker compose logs --tail=50

# 7. 滚动更新(有变更时)
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
# docker-stack.yml(使用 deploy 块)
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
# 初始化 swarm(如未初始化)
docker swarm init --advertise-addr 192.168.1.100

# 部署 stack
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 # 每次并行更新 2 个副本
delay: 10s # 每组更新后等待 10 秒
order: start-first # 先启动新版本,再停止旧版本
failure_action: rollback # 更新失败自动回滚
monitor: 30s # 监控新容器 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
# 1. 查看配置是否正确解析
docker compose -f docker-compose.yml -f docker-compose.prod.yml config

# 2. 查看实时日志(加时间戳、tail 追踪)
docker compose logs -f --tail=100 --timestamps app

# 3. 进入容器内部分析
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_"

# 导出 compose 配置(调试变量替换)
docker compose -f docker-compose.yml -f docker-compose.prod.yml config > /tmp/resolved.yml

# 查看挂载的 volume 实际路径
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
# docker-compose.yml —— 生产级模板
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