Docker Compose 不只是启动容器:一套可落地的生产化配置思路
很多项目的 compose.yaml 最初只有几行:应用容器、数据库容器、端口映射,再加一句 docker compose up -d。开发环境里这已经足够,但到了长期运行的服务器上,很快会遇到新的问题:
- 数据库启动了,应用却因为数据库尚未就绪而反复报错。
- 容器进程还活着,接口实际已经无法提供服务。
- 日志无限增长,把服务器磁盘写满。
- 配置、密码和镜像版本混在一个文件里。
- 发布新版本时直接停掉全部服务。
- 数据卷存在,却从来没有验证过恢复流程。
Compose 可以用于单机和小规模生产部署,但前提是把它当成运行编排工具,而不是批量执行 docker run 的快捷方式。本文给出一套从“能启动”走向“可维护”的配置思路。

1. 先明确 Compose 的适用边界
Docker Compose 比较适合:
- 单机部署的中小型服务。
- 内部工具、管理后台和低到中等流量应用。
- 对复杂弹性伸缩和跨节点调度要求不高的系统。
- 希望保持部署简单、成本可控的团队。
如果系统需要跨主机调度、自动扩缩容、复杂滚动发布、节点故障迁移和大规模服务治理,通常应该评估 Kubernetes、Nomad 或云平台托管服务。
选择 Compose 并不等于“不专业”。真正重要的是:当前工具能否覆盖故障模型、可用性目标和团队维护能力。
2. 不要使用浮动镜像标签
下面的配置看起来方便:
services:
api:
image: example/api:latest
但 latest 并不代表最新,也不能保证两次部署拉取到同一个镜像。生产环境应使用明确版本:
services:
api:
image: example/api:1.8.3
要求更严格时,可以固定镜像摘要:
services:
api:
image: example/api@sha256:xxxxxxxxxxxxxxxx
固定版本带来的价值是可审计、可复现、可回滚。发布系统应该明确记录“哪个版本在什么时间被部署到了哪台机器”。
3. depends_on 不等于服务已经可用
常见写法:
services:
api:
depends_on:
- db
它只能表达启动顺序,不能保证数据库已经完成初始化并接受连接。更可靠的做法是为依赖服务增加健康检查:
services:
db:
image: postgres:17.5
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
api:
image: example/api:1.8.3
depends_on:
db:
condition: service_healthy
即使配置了依赖健康状态,应用本身仍应具备连接重试能力。因为数据库可能在系统运行期间重启,启动顺序无法解决运行时故障。
4. 为应用定义真正有意义的健康检查
健康检查不应该只验证进程存在,也不应该无条件返回 200。一个合理的接口可以分成两类:
- 存活检查:进程是否还能够处理请求。
- 就绪检查:数据库、缓存或必要依赖是否达到服务条件。
Compose 示例:
services:
api:
image: example/api:1.8.3
healthcheck:
test: ["CMD", "curl", "-fsS", "http://localhost:8080/health/ready"]
interval: 15s
timeout: 3s
retries: 3
start_period: 30s
注意镜像中必须实际存在 curl。如果为了健康检查额外安装大量工具并不划算,可以使用应用自带的命令、轻量探针程序,或者由反向代理从外部检查。
健康检查还要避免执行昂贵 SQL,否则检查本身可能成为压力来源。
5. 配置重启策略,但不要掩盖持续崩溃
常用配置:
services:
api:
restart: unless-stopped
它能在进程异常退出或 Docker 服务重启后恢复容器。但重启策略不是异常处理系统。如果应用因为配置错误每隔几秒崩溃一次,无限重启只会制造大量日志。
生产环境至少要监控:
- 容器重启次数。
- 最近一次退出码。
- 健康状态变化。
- OOM 和磁盘不足事件。
- 启动失败后的连续退避情况。
自动恢复解决的是短暂故障,告警解决的是持续故障。
6. 给资源设置边界
没有资源限制的容器,可以占满整台主机的内存或 CPU。对于 Compose,本地运行时可以使用服务级资源配置:
services:
api:
image: example/api:1.8.3
cpus: 1.5
mem_limit: 1g
mem_reservation: 512m
具体支持项与 Compose 实现、运行模式有关,部署前应使用当前环境的 docker compose config 检查最终配置,并通过压力测试验证限制是否生效。
资源限制不能凭感觉设置。建议结合:
- 稳态内存和峰值内存。
- GC 或运行时堆配置。
- 单请求 CPU 消耗。
- 并发连接数与线程数。
- 主机上其他服务的资源需求。
内存上限过低会频繁 OOM,过高则无法保护主机。比较稳妥的做法是先观测,再逐步收紧。
7. 只暴露真正需要的端口
数据库通常不需要直接暴露到公网:
services:
db:
image: postgres:17.5
expose:
- "5432"
对外入口交给反向代理:
services:
proxy:
image: nginx:1.28-alpine
ports:
- "80:80"
- "443:443"
api:
expose:
- "8080"
Compose 默认会为项目创建网络,服务可通过服务名互相访问。可以进一步拆分前端网络和数据网络,让代理不能直接访问数据库:
networks:
frontend:
backend:
internal: true
网络隔离不能替代主机防火墙、云安全组和数据库权限控制,但能减少不必要的暴露面。
8. 配置与秘密信息分开管理
不要把密码直接提交到仓库:
environment:
DB_PASSWORD: super-secret
可以通过环境文件注入非敏感配置:
env_file:
- .env.production
对于密码、令牌和证书,更适合使用只读文件挂载、Compose secrets,或者外部秘密管理服务:
secrets:
db_password:
file: ./secrets/db_password.txt
services:
api:
secrets:
- db_password
应用读取 /run/secrets/db_password,可以减少秘密出现在命令行参数和普通环境变量中的机会。秘密文件本身仍需设置严格权限,并确保不会进入 Git、镜像构建上下文和备份日志。
9. 文件系统尽量只读
如果应用不需要写入根文件系统,可以配置:
services:
api:
read_only: true
tmpfs:
- /tmp
需要持久化的数据应写入明确的数据卷:
volumes:
- app_uploads:/app/uploads
这样能减少程序误写、攻击落地文件和临时数据污染镜像层的风险。启用只读前,要确认应用的缓存、PID、临时文件和上传目录分别应该放在哪里。
还可以配合:
security_opt:
- no-new-privileges:true
并尽量使用非 root 用户运行应用。容器不是安全边界的终点,但每一层约束都会降低风险。
10. 限制日志大小
默认日志长期增长,是单机 Compose 最常见的事故之一。可以使用日志轮转:
services:
api:
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
应用日志建议写到标准输出和标准错误,由容器运行时统一收集。不要同时在容器内部写无限增长的日志文件。
需要集中检索时,可以接入 Loki、Elasticsearch、云日志服务或其他采集方案。无论使用哪种工具,都应在日志中保留:
- 时间戳和日志级别。
- 请求 ID 或 Trace ID。
- 服务名与版本号。
- 错误类型和关键上下文。
同时避免记录密码、访问令牌和完整个人敏感信息。
11. 数据卷不等于备份
下面的配置只能保证容器删除后数据仍保存在卷中:
volumes:
db_data:
services:
db:
volumes:
- db_data:/var/lib/postgresql/data
它不能防止误删除、文件损坏、磁盘故障或主机丢失。真正的备份方案至少要明确:
- 备份方式:逻辑备份、物理备份或快照。
- 备份频率与保留周期。
- 备份文件存放在另一台机器或对象存储。
- 加密、访问权限和完整性校验。
- 定期执行恢复演练。
没有恢复验证的备份,只是一份尚未证明可用的文件。
12. 优雅停止决定发布是否丢请求
容器停止时,Docker 会先发送终止信号,再等待一段时间后强制结束。应用需要正确处理信号:
- 停止接收新请求。
- 等待正在执行的请求完成。
- 停止消费新的队列消息。
- 提交或回滚事务。
- 关闭数据库连接和文件句柄。
Compose 中可以配置:
services:
api:
stop_grace_period: 30s
宽限时间要大于正常请求或任务的收尾时间,但不能无限延长。对于长任务,通常应该设计可恢复进度、幂等消费和超时机制,而不是只依赖更长的停止等待。
13. Compose 本身不会自动提供无损滚动发布
直接执行:
docker compose up -d
可能会重建发生变化的容器。单实例服务在切换期间可能出现短暂中断。需要更高可用性时,可以采用:
- 反向代理前运行两个应用实例,逐个替换。
- 蓝绿部署:新旧两套 Compose 项目并行运行,验证后切换入口。
- 在负载均衡器中先摘除旧实例,再停止容器。
- 数据库变更使用向前兼容的扩展与收缩策略。
例如新增字段时,先发布兼容新旧结构的代码,再执行迁移,最后清理旧字段。不要让应用发布与不可逆数据库变更绑定成一次豪赌。
14. 一份更完整的 Compose 骨架
name: demo-prod
services:
proxy:
image: nginx:1.28-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
networks:
- frontend
depends_on:
api:
condition: service_healthy
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
api:
image: example/api:1.8.3
restart: unless-stopped
env_file:
- .env.production
secrets:
- db_password
expose:
- "8080"
networks:
- frontend
- backend
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "/app/healthcheck"]
interval: 15s
timeout: 3s
retries: 3
start_period: 30s
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
cpus: 1.5
mem_limit: 1g
stop_grace_period: 30s
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
db:
image: postgres:17.5
restart: unless-stopped
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- db_data:/var/lib/postgresql/data
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
networks:
frontend:
backend:
internal: true
volumes:
db_data:
secrets:
db_password:
file: ./secrets/db_password.txt
这份骨架仍然需要根据应用调整,但它已经覆盖了版本固定、健康检查、网络隔离、秘密文件、资源边界、只读文件系统、优雅停止和日志轮转等关键问题。
部署前可以先执行:
docker compose config
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=200
docker compose config 能帮助发现变量未定义、合并结果异常和配置格式问题,但不能替代真实环境验证。
15. 上线前 Checklist
- 镜像是否固定版本,并能快速回滚?
- 应用和依赖是否都有健康检查?
- 应用是否能处理依赖在运行期间重启?
- 端口是否只暴露必要入口?
- 密码和证书是否脱离仓库与镜像?
- 容器是否使用非 root、只读文件系统和最小权限?
- CPU、内存和日志是否有上限?
- 数据是否有异机备份,并完成恢复演练?
- 应用能否优雅处理停止信号?
- 发布和数据库迁移是否向前兼容?
- 是否监控主机磁盘、容器重启、健康状态和接口延迟?
总结
生产化 Compose 的重点,不是把 YAML 写得更长,而是提前回答这些问题:服务何时算真正可用、故障后如何恢复、资源耗尽时谁被保护、数据丢失后如何恢复、发布过程中怎样避免中断。
当镜像版本、健康检查、资源限制、网络边界、日志轮转、备份恢复和发布策略都被明确下来,Compose 才从一个启动工具变成一套可维护的运行方案。
更多推荐

所有评论(0)