Docker Compose 部署实战:从开发到生产的配置演进
Docker Compose 是"能用"和"好用"差距最大的工具之一。本文从一份能跑起来的 docker-compose.yml 出发,逐步加入生产环境需要的配置:日志轮转、健康检查、网络隔离、数据备份、CI/CD 集成。附可复用的生产级配置模板。【点击查看生产级配置】
先说结论:Compose 是中小项目的最佳平衡点
Docker Compose 可能是 Docker 生态里最被低估的工具——不是因为功能不够强,而是因为大多数人只用了它 20% 的能力。
本文从一份”能跑”的 docker-compose.yml 开始,逐步演进到一份”生产就绪”的配置。每步都说明为什么加、加了以后解决什么问题。
第一阶段:能跑(开发环境)
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: secret
这能跑,但仅限本地开发。问题很多:密码明文、没持久化、没健康检查、日志不限制、网络不隔离。直接上生产的话,几天内就会出问题。
第二阶段:加持久化和配置管理
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
env_file: .env
depends_on:
db:
condition: service_healthy
networks:
- app-net
db:
image: postgres:16
env_file: .env
volumes:
- pg-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-net
volumes:
pg-data:
networks:
app-net:
三个关键改动:
-
命名卷:
pg-data是 Docker 管理的命名卷,不会因为docker compose down丢失数据。比 bind mount 更安全(不需要关心宿主机路径),而且方便备份。 -
健康检查:
depends_on加了condition: service_healthy,确保 app 服务在数据库真正就绪后才启动,而不是在容器启动后就启动。不这么做的话,app 启动时数据库还没准备好,会导致 app 崩溃重启。 -
网络隔离:创建一个内部网络
app-net,只有这个网络里的服务才能互相访问。外部的请求只能通过 app 暴露的 3000 端口进入,数据库不暴露端口,安全性高了一个等级。
第三阶段:加日志轮转和重启策略
services:
app:
build: .
env_file: .env
restart: unless-stopped
logging:
driver: "local"
options:
max-size: "10m"
max-file: "3"
depends_on:
db:
condition: service_healthy
networks:
- app-net
db:
image: postgres:16
env_file: .env
restart: unless-stopped
logging:
driver: "local"
options:
max-size: "10m"
max-file: "3"
volumes:
- pg-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-net
日志轮转是最容易被忽视的配置。Docker 默认的 json-file 驱动会把所有日志写到一个文件里,不做任何限制。如果容器每天产生几百 MB 日志,几天后就会占满磁盘。这不是理论问题——我见过好几次因为日志撑爆磁盘导致服务宕机的事故。
配置 logging.driver: "local" 并限制 max-size: 10m 和 max-file: 3,每个容器最多保留 30MB 日志,轮转自动进行。
restart: unless-stopped 确保服务在崩溃后自动重启,但如果你手动停了它,Docker 不会自作主张给你重启。
第四阶段:加备份和运维脚本
上面的配置日常运行够用了,但还有一个重要的事没做——数据库备份。
#!/bin/bash
# backup-db.sh - 在宿主机上通过 cron 定时执行
BACKUP_DIR=/var/backups/postgres
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DB_CONTAINER=$(docker compose ps -q db)
docker exec $DB_CONTAINER pg_dump -U $POSTGRES_USER $POSTGRES_DB | gzip > $BACKUP_DIR/db_$TIMESTAMP.sql.gz
# 保留最近 30 天的备份,更早的自动删除
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete
然后设置 cron:
0 3 * * * /usr/local/bin/backup-db.sh
每天凌晨 3 点自动备份,保留 30 天。如果数据重要,还可以加一步把备份上传到对象存储:
aws s3 cp $BACKUP_DIR/db_$TIMESTAMP.sql.gz s3://my-backups/db/
第五阶段:加 CI/CD 集成
到这一步,部署应该变成一条命令:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and deploy
run: |
docker compose build
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
这里用了 docker-compose.prod.yml 覆盖生产环境的配置差异:
# docker-compose.prod.yml
services:
app:
environment:
- NODE_ENV=production
restart: always
deploy:
resources:
limits:
memory: 512M
生产环境覆盖文件只写和环境相关的差异,核心配置留在 docker-compose.yml 里,避免两份配置不一致。
总结:一份生产就绪的 Compose 配置清单
| 配置项 | 开发环境 | 生产环境 |
|---|---|---|
| 持久化 | 不必须 | 命名卷 |
| 健康检查 | 不必须 | 必须 |
| 日志轮转 | 不必须 | 必须 |
| 重启策略 | 不设置 | unless-stopped |
| 网络隔离 | 不必须 | 必须 |
| 资源限制 | 不必须 | 建议 |
| 数据库备份 | 不必须 | 必须(cron + 远端存储) |
| CI/CD | 不必须 | 推荐 |
Docker Compose 不是玩具,它的上限取决于你配置的完整度。 一份配了日志轮转、健康检查、网络隔离和备份策略的 Compose 文件,可以在单机部署场景下稳定跑很久。
需要 Docker 部署方案设计或 CI/CD 流水线搭建?联系我们,说清你的服务架构和部署环境,24 小时内回可行性。
相关阅读
- 运维自动化脚本模式:从一次性脚本到可维护工具 —— Docker 部署后的自动化运维配套
- CI/CD 工具选型:GitHub Actions vs GitLab CI —— 与 Docker Compose 配合的 CI/CD 流水线选型
常见问题
docker-compose.yml 适合生产环境吗?
适合中小规模项目。Docker Compose 的定位是单机编排,不是集群调度。如果你的项目部署在单台服务器上,或者只需要 3-5 个容器协作,Compose 是够用的,而且运维成本远低于 K8s。当你的服务数量超过 10 个、需要多机部署或自动扩缩容时,才值得考虑 K8s 或 Nomad。
.env 文件里的敏感信息怎么处理?
.env 文件不应该提交到 Git 仓库(加到 .gitignore)。生产环境的敏感信息建议用 Docker Secrets 或环境变量注入的方式传递。小团队可以用一个安全的 vault 脚本在部署时生成 .env 文件,CI/CD 工具(如 GitHub Actions Secrets)也可以直接注入环境变量,避免明文存储。
容器日志怎么处理?不处理会有什么问题?
默认的 json-file 驱动会将所有日志写到一个 JSON 文件里,不做任何限制。如果容器每天产生几百 MB 日志,几天后就会占满磁盘。生产环境一定要配置日志轮转:在 docker-compose.yml 里设置 logging.driver 为 local 或 json-file,并限制 max-size(如 10m)和 max-file(如 3)。更彻底的方案是使用日志采集工具(如 Loki + Promtail 或 Filebeat)将日志统一发送到集中存储。
容器里的数据库怎么备份?
不要直接在容器里跑 mysqldump 然后指望它持久化。推荐的做法是:① 数据库数据挂载到宿主机的命名卷(named volume),方便直接备份卷文件;② 写一个独立的备份脚本,在宿主机上定时执行(cron),用 docker exec 进入容器执行数据库导出,然后压缩上传到对象存储(如 S3、OSS);③ 定期验证备份的可恢复性——不能恢复的备份等于没有备份。