2026 年还能用 Docker Compose 跑生产环境吗?实操指南与避坑大全

发布: 2026-05-05 • 阅读: 12 分钟 • 标签: Docker, Docker Compose, DevOps, 生产部署, Kubernetes

一个老问题的新答案

2026 年问"能不能用 Docker Compose 跑生产" 就像问 "能不能用摩托车跑高速"

答案取决于你的场景 如果你的业务是单节点部署 — 给客户分发一套自托管应用、跑一个内部工具、部署边缘节点 — Docker Compose 完全没问题 事实上很多公司在用 而且运行得很稳

但关键在于 Docker Compose 本身只是一个声明式容器编排工具 它不会帮你处理运维层面的问题 你需要自己填上那些缺口

这篇文章整理了 7 个最常见的 Docker Compose 生产坑和对应的解决方案 每个方案都配有实际可用的 docker-compose.yml 片段和命令

坑一:Orphan 容器 —— 删除服务后容器还在跑

从 docker-compose.yml 里删掉一个服务 跑 docker compose up -d 后旧的容器并不会自动停止 它变成了 "orphan"(孤儿容器) 仍然占用端口和内存 而 docker compose ps 不会显示它

半年后你可能会发现一个早已删除的工作进程在悄悄消耗资源

# 正确的启动方式
docker compose up -d --remove-orphans
# 停止时也清理
docker compose down --remove-orphans

建议把这个 flag 写入你的部署脚本或 CI/CD pipeline 的每一步 Compose 操作中 另外可以用一个定时任务做定期清理

# 每天凌晨清理所有项目的 orphan 容器
0 3 * * * docker ps -q --filter "status=exited" | xargs -r docker rm

注意 named volume 不会被 --remove-orphans 删除 需要手动清理 dangling volume

docker volume ls --filter dangling=true
docker volume prune

坑二:健康检查只检测不处理

Docker Compose 支持 healthcheck 配置 但它默认只告诉你容器是否健康 不会自动重启不健康的容器

很多人的配置是这样的

services:
  web:
    image: myapp:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 10s
      retries: 3

问题在于如果健康检查连续失败 3 次 容器被标记为 unhealthy 但它仍然在运行 Compose 什么也不会做

解决方案是用 restart: always 配合 Docker 引擎的自动重启策略 但这只对容器崩溃有效 对健康检查失败无效

真正的解法

services:
  web:
    image: myapp:latest
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
  # 用一个 sidecar 容器做自动修复
  health-watcher:
    image: docker:27
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    command: >
      sh -c "while true; do
        if [ \"$(docker inspect --format='{{json .State.Health.Status}}' web_1)\" = '\"unhealthy\"' ]; then
          docker restart web_1;
        fi;
        sleep 60;
      done"

或者更简单的方式:在宿主机上配置 systemd timer + Docker API 检查 或者使用 Distr 等专门的 Docker 运维 agent 来自动处理

坑三::latest 标签 —— 镜像漂移

:latest 标签部署 某天重新 pull 后获取了一个不兼容的新版本 导致服务崩溃

这是生产环境中最常见也最容易避免的问题

# ❌ 不推荐
image: myapp:latest
# ✅ 推荐:使用具体版本
image: myapp:2.5.1
# ✅ 也可以用 SHA256 摘要(最精确)
image: myapp@sha256:a1b2c3d4e5f6...

在 CI/CD 中构建镜像时 确保同时打上版本标签和 :latest 但在生产部署时只用版本标签

# GitHub Actions 示例
- name: Build and push
  run: |
    docker build -t registry.example.com/myapp:${{ github.sha }} .
    docker tag registry.example.com/myapp:${{ github.sha }} registry.example.com/myapp:latest
    docker push --all-tags registry.example.com/myapp

部署时使用明确的 commit SHA 或语义化版本 这样回滚时也知道精确的版本号

坑四:磁盘写满 —— Compose 不会帮你清理

Docker Compose 不管理磁盘空间 日志文件、旧镜像、匿名 volume 会无限增长直到磁盘写满

你需要主动配置 Docker 引擎层面的日志限制和清理策略

# 在 /etc/docker/daemon.json 中配置全局日志限制
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2"
}
# 重启 Docker 生效
sudo systemctl restart docker

在 docker-compose.yml 中也可以为每个服务单独配置

services:
  web:
    image: myapp:2.5.1
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

定期清理脚本

# 每周日凌晨清理
0 4 * * 0 docker image prune -af
0 4 * * 0 docker builder prune -af
0 5 * * 0 docker volume prune -f

建议设置磁盘告警 在磁盘使用率达到 80% 时通知你

# 简单的磁盘检查脚本(配合 crontab 每 5 分钟跑一次)
#!/bin/bash
THRESHOLD=80
USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$USAGE" -gt "$THRESHOLD" ]; then
  echo "Disk usage at ${USAGE}% on $(hostname)" | mail -s "Disk Alert" admin@example.com
fi

坑五:Socket 挂载 —— 容器逃逸风险

/var/run/docker.sock 挂载进容器(Portainer、Watchtower 等工具需要) 本质上就是把宿主机的 root 权限给了容器 如果该容器被攻破 攻击者可以直接控制宿主机 Docker 守护进程

这是一个严重的安全隐患 但很多人为了便利而忽略了

缓解方案

第一 如果可能 用 Docker API over TCP + TLS 替代 socket 挂载

第二 使用 docker:socket-proxy 镜像做一个只读的 socket 代理 只暴露必要的 API 端点

services:
  socket-proxy:
    image: tecnativa/docker-socket-proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    environment:
      CONTAINERS: 1    # 只允许容器相关操作
      INFO: 1         # 允许只读 info
      POST: 0         # 禁止写操作
      NETWORKS: 0
      VOLUMES: 0

第三 如果必须挂载 用 :ro(只读)模式 并且最小化容器内的用户权限

坑六:日志没有轮转 —— 单文件撑爆磁盘

默认的 json-file 日志驱动不会自动轮转 一个大流量的 Nginx 容器一天就能写几十 GB 日志

前面我们已经配置了全局日志限制 但还有一个更容易被忽略的问题:Compose 使用的 docker compose logs 命令默认不会轮转日志文件

推荐切换到 local 日志驱动 它自带轮转且性能更好

# daemon.json
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "5"
  }
}

或者使用 journald 驱动 把容器日志纳入 systemd journal 统一管理

services:
  nginx:
    image: nginx:1.26
    logging:
      driver: journald
      options:
        tag: "nginx-{{.Name}}"

这样可以用 journalctl CONTAINER_NAME=nginx-1 查看日志 也支持自动轮转

坑七:没有备份和恢复策略

Docker Compose 不提供数据备份功能 生产环境的数据库(PostgreSQL、MySQL、Redis)必须要有自动备份方案

# docker-compose.yml 中的数据库备份 sidecar
services:
  postgres:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./backup:/backup
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
  pg-backup:
    image: postgres:16
    volumes:
      - ./backup:/backup
    entrypoint: |
      sh -c '
      while true; do
        sleep 86400;
        PGPASSWORD=${DB_PASSWORD} pg_dump -h postgres -U postgres mydb > /backup/db_$(date +%Y%m%d).sql;
        find /backup -name "*.sql" -mtime +7 -delete;
      done'
    depends_on:
      postgres:
        condition: service_healthy

对于重要数据 建议同时备份到远程存储(S3、B2 等)

# 用 s3cmd 上传到 S3 兼容存储
#!/bin/bash
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
docker exec postgres pg_dump -U postgres mydb > /tmp/backup_$TIMESTAMP.sql
gzip /tmp/backup_$TIMESTAMP.sql
s3cmd put /tmp/backup_$TIMESTAMP.sql.gz s3://my-backups/db/
rm /tmp/backup_$TIMESTAMP.sql.gz

测试恢复流程和备份本身一样重要 每个月至少做一次恢复演练

生产级 Docker Compose 模板

结合以上经验 这里提供一个可直接投入生产的 docker-compose.yml 模板

version: "3.9"
services:
  app:
    image: registry.example.com/myapp:${APP_VERSION:-latest}
    restart: unless-stopped
    ports:
      - "443:443"
    volumes:
      - app-data:/data
    env_file: .env.production
    logging:
      driver: "local"
      options:
        max-size: "10m"
        max-file: "5"
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s
    deploy:
      resources:
        limits:
          cpus: "2"
          memory: "2G"
        reservations:
          cpus: "0.5"
          memory: "512M"
    networks:
      - internal
      - public
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
    env_file: .env.production
    logging:
      driver: "local"
      options:
        max-size: "10m"
        max-file: "3"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: "1G"
    networks:
      - internal
  nginx:
    image: nginx:1.26-alpine
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    logging:
      driver: "journald"
      options:
        tag: "nginx-{{.Name}}"
    depends_on:
      app:
        condition: service_healthy
    networks:
      - public
volumes:
  app-data:
    driver: local
  pgdata:
    driver: local
networks:
  internal:
    driver: bridge
    internal: true
  public:
    driver: bridge

这个模板涵盖了:资源限制、健康检查、日志轮转、网络隔离(数据库放在 internal 网络)、卷持久化

什么时候该升级到 Kubernetes

Docker Compose 最合适的场景是单节点部署 当出现以下信号时 可以考虑迁移到 Kubernetes

  • 需要多节点高可用(单个宿主机挂了服务就不可用)
  • 需要自动扩缩容(流量波动大 手动调整太慢)
  • 需要更细粒度的资源调度和隔离
  • 需要声明式的滚动更新和自动回滚
  • 团队规模增长 需要标准化的编排平台

但注意 Kubernetes 本身也引入了新的复杂度 如果你的团队没有专职的 SRE 或 DevOps 工程师 维持一个小型 K8s 集群可能比管理 Docker Compose 更痛苦

Docker Swarm 是另一个选项 它在多节点场景下的复杂度介于 Compose 和 K8s 之间 但生态活跃度远不如 K8s

总结

2026 年的答案是:Docker Compose 完全可以跑生产 但你需要为它补上运维层面的功能

核心 checklist

  • 始终使用 --remove-orphans
  • 配置 healthcheck 加上自动恢复机制
  • 固定镜像版本 不用 :latest
  • 设置日志轮转和磁盘清理
  • 最小化 socket 挂载 优先用只读代理
  • 单独配置数据库自动备份
  • 设置磁盘使用率告警
  • 定期测试恢复流程

Docker Compose 不是最时髦的工具了 但它足够简单、稳定 在合适的场景下它仍然是最好的选择