Docker Compose 多环境配置:dev test prod 分离实战

容器化部署已成标配,而 Docker Compose 则是本地开发、测试环境和小型生产部署的事实标准。但很多团队的 Compose 文件只是一个能跑起来的”大一统”配置——开发、测试、生产共用同一份 docker-compose.yml,导致环境差异带来的坑层出不穷。本文将演示如何用 Docker Compose 实现dev/test/prod 三环境分离,既保持配置的可维护性,又确保各环境的行为严格隔离。

为什么需要多环境配置分离?

先看一个典型痛点:开发环境里数据库用内存型 SQLite 就够了,线上必须用 PostgreSQL;测试环境需要开启详细日志方便排查,线上却要求日志级别调低、输出到外部集中收集系统。如果所有环境共用同一份 Compose 文件,只能通过臃肿的 if/else 式注释和覆盖配置来区分,随着环境增多,文件会变得难以维护。

环境数据库日志级别资源限制调试工具
开发(dev)SQLite / 本地 PostgresDEBUG宽松(本地机器够用)Hot-reload、inspect
测试(test)PostgreSQL(同版本)INFO接近生产 80%集成测试框架
生产(prod)PostgreSQL(HA 集群)WARN严格限流无,禁止调试入口

可以看出三个环境的差异非常显著——把这些差异塞进一个文件,结果必然是混乱。正确的做法是分层配置 + 按需合并

方案一:独立 Compose 文件 + include 引入

Docker Compose V2 引入了 include 指令,允许主配置文件动态加载其他 compose 文件,这是实现环境分离最现代也最清晰的方式。

2.1 项目目录结构

docker-compose/
├── base/                  # 公共基础配置(所有环境共用)
│   └── docker-compose.base.yml
├── dev/                   # 开发环境覆盖配置
│   └── docker-compose.dev.yml
├── test/                  # 测试环境覆盖配置
│   └── docker-compose.test.yml
├── prod/                  # 生产环境覆盖配置
│   └── docker-compose.prod.yml
└── docker-compose.yml     # 主入口(按需 include)

2.2 base 公共配置

# base/docker-compose.base.yml
services:
  app:
    build:
      context: ../
      dockerfile: Dockerfile
    image: myapp:${TAG:-latest}
    restart: unless-stopped
    networks:
      - appnet
    depends_on:
      - db
      - redis

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DB_USER:-appuser}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
      POSTGRES_DB: ${DB_NAME:-myapp}
    volumes:
      - pgdata:/var/lib/postgresql/data
    networks:
      - appnet
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    networks:
      - appnet

volumes:
  pgdata:

networks:
  appnet:
    driver: bridge

2.3 dev 开发环境覆盖

# dev/docker-compose.dev.yml
include:
  - ../base/docker-compose.base.yml

services:
  app:
    build:
      context: ../
      dockerfile: Dockerfile.dev   # 开发专用 Dockerfile(含调试工具)
    environment:
      NODE_ENV: development
      LOG_LEVEL: debug
      DEBUG: 'app:*'
    volumes:
      - ../src:/app/src            # 源码热重载挂载
      - /app/node_modules          # 覆盖 node_modules 避免宿主机污染
    ports:
      - "3000:3000"                # 业务端口
      - "9229:9229"                # Node.js inspect 调试端口
    command: ["npm", "run", "dev"] # 启动开发服务器

  db:
    ports:
      - "5432:5432"                # 允许本地直连数据库

  redis:
    ports:
      - "6379:6379"

2.4 prod 生产环境覆盖

# prod/docker-compose.prod.yml
include:
  - ../base/docker-compose.base.yml

services:
  app:
    image: myapp:${VERSION:-stable}  # 使用预构建的稳定镜像
    environment:
      NODE_ENV: production
      LOG_LEVEL: warn
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    restart: always
    ports:
      - "8080:8080"                  # 仅暴露生产端口
    # 生产环境禁止调试端口
    # command: 不覆盖,使用 Dockerfile 的 ENTRYPOINT

  db:
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}  # 从 secrets 或 .env 注入
    volumes:
      - pg_prod_data:/var/lib/postgresql/data  # 独立持久卷

volumes:
  pg_prod_data:                          # 生产数据卷,与开发环境完全隔离

方案二:基于 .env 的环境变量隔离

除了分文件,也可以用环境文件(.env)配合 Compose 的 env_file 指令实现差异。这种方式适合差异较小的场景,比如只调整资源限制或环境变量:

# 各自目录下的 .env 文件
dev/.env:
  NODE_ENV=development
  DB_HOST=db-dev
  LOG_LEVEL=debug
  APP_PORT=3000
  DEBUG_MODE=true

test/.env:
  NODE_ENV=test
  DB_HOST=db-test
  LOG_LEVEL=info
  APP_PORT=3000
  DEBUG_MODE=false

prod/.env:
  NODE_ENV=production
  DB_HOST=db-prod
  LOG_LEVEL=warn
  APP_PORT=8080
  DEBUG_MODE=false
  # 敏感信息通过 Docker secrets 或 CI/CD 注入,不写在 .env 中

方案对比:两种策略如何选择

维度独立文件 + include.env 环境变量隔离
配置差异程度差异大(服务数量、网络、卷都不一样)差异小(主要是环境变量和资源限制)
文件可维护性⭐⭐⭐ 各自职责清晰⭐⭐ 单文件越来越臃肿
新环境扩展成本⭐⭐⭐ 新增一个目录即可⭐⭐ 需要修改主文件
CI/CD 集成⭐⭐⭐ 每个环境独立构建/部署⭐⭐ 需要传递不同 .env
适用项目规模中大型项目、多环境严格隔离小型项目、环境差异有限的场景

生产环境安全加固要点

生产环境的 Compose 配置除了功能正确,还需要额外的安全考量。以下是经过实战验证的安全清单:

6.1 禁止暴露调试端口

# ❌ 错误:生产环境暴露了调试端口
services:
  app:
    ports:
      - "8080:8080"
      - "9229:9229"   # 绝不应在生产环境暴露

# ✅ 正确:仅暴露业务端口
services:
  app:
    ports:
      - "8080:8080"

6.2 使用 Docker Secrets 管理敏感信息

# 不使用明文环境变量传递密码
# ❌ 错误做法
services:
  db:
    environment:
      POSTGRES_PASSWORD: supersecretpassword123  # 明文出现在 compose 文件里!

# ✅ 正确做法:使用 Docker Secrets
services:
  db:
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt   # 文件本身应从 git 仓库中排除(.gitignore)

6.3 资源限制与 OOM 防护

services:
  app:
    image: myapp:stable
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    # 确保 OOM 时自动重启,但设置重试次数限制避免雪崩
    restart: on-failure:5

一键切换环境的实用命令

# 启动开发环境(含热重载 + 调试端口)
docker compose --profile dev up -d
# 或者显式指定
docker compose -f docker-compose.yml -f dev/docker-compose.dev.yml up -d

# 启动测试环境
docker compose -f docker-compose.yml -f test/docker-compose.test.yml up -d

# 启动生产环境
docker compose -f docker-compose.yml -f prod/docker-compose.prod.yml up -d

# 查看所有可用 profile
docker compose config --profiles

总结

Docker Compose 多环境配置的核心理念是「公共配置下沉,环境差异上提」。通过 include 机制将基础服务配置和环境的差异化配置彻底解耦,既能保证各环境行为严格隔离,又能在公共层统一维护基础设施(网络、卷、健康检查等),大幅降低运维复杂度。

如果你的项目还在用一份 docker-compose.yml 走天下,不妨参考本文的结构做一次重构。关于容器编排的更多实战内容,可以参考之前的 Docker 网络模式详解K8s 入门教程——Compose 是 K8s 的前置基础,理解了网络模式和多环境配置,迁移到 Kubernetes 会顺畅得多。更多技术干货,欢迎关注 FSData!🔧

上一篇 TypeScript 高级类型体操:Utility Types 到泛型约束详解
下一篇 服务器安全加固:SSH / 防火墙 / Fail2ban 实战指南