Docker Compose 多容器部署:构建完整的应用编排方案

一个典型的 Web 应用很少只有一个进程:前端要 Docker 容器跑、后端接口要一个、数据库一个、缓存一个,可能还挂着一个后台任务队列。在本地把这些服务一个个 docker run 起来,端口、网络、依赖顺序都要记在脑子里;换个同事接手,又要重新梳理一遍。Docker Compose 解决的就是这个痛点。如果你还不熟悉 Docker 本身,可以先看 Docker 部署指南;这里我们直接用 Compose 来定义整个应用栈——一个 YAML 文件把服务写清楚,一条命令全部拉起,全团队共享同一套启动方式。

一、基本用法

在项目根目录放一个 docker-compose.yml,声明三个服务:Web 应用、PostgreSQL 数据库和 Redis 缓存:

version: '3.8'

services:
  web:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=development
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    volumes:
      - postgres-data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: myapp
      POSTGRES_PASSWORD: secret

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

volumes:
  postgres-data:

几个值得注意的点:build: . 表示 web 服务用当前目录的 Dockerfile 构建;depends_on 声明启动顺序——先启动 db 和 redis,再启动 web,但注意它只保证「先启动」,不保证「已就绪」,真正等数据库可用要靠后面的健康检查。postgres-data 是命名卷,数据库数据存在里面,容器删了数据还在。

二、网络配置

Compose 默认会给项目建一个网络,所有服务通过服务名互相访问。也就是说 web 容器里连数据库,用 db:5432 而不是 localhost:5432。这对多环境、多项目隔离很有用:两个项目都叫 web,也不会冲突,因为各自的网络是分开的。

需要更细的控制时,可以自定义网络,把服务按层级隔离:

services:
  frontend:
    networks:
      - frontend
      - backend

  api:
    networks:
      - backend

  db:
    networks:
      - backend

networks:
  frontend:
  backend:

上面这个配置里,frontend 能访问 frontend 和 backend 两个网段,api 和 db 只在 backend 里。这样即使某个服务出了问题被攻破,它能接触的网络范围也是受限的。

Compose 还支持把「服务数量」写进配置:web 服务定义 deploy.replicas: 2docker compose up --scale web=3 可以临时扩到 3 个副本。虽然 Compose 的 scale 不是生产级的负载均衡,但对本地压测、演示环境来说够用——这也是很多人用 Compose 做「伪生产」演练的原因。

三、环境管理与多配置文件

开发和生产的环境变量差别很大:调试开关、日志级别、端口映射都不一样。Compose 支持按环境拆分文件,用 -f 叠加:

# docker-compose.override.yml (开发环境)
services:
  web:
    environment:
      - DEBUG=true
    volumes:
      - .:/app

# docker-compose.prod.yml (生产环境)
services:
  web:
    environment:
      - NODE_ENV=production
      - DEBUG=false
# 开发环境
docker compose up

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

开发环境把当前目录挂载进容器,改代码即时生效;生产环境用构建好的镜像、关闭调试。敏感配置用 .env 文件管理,Compose 会自动读取同目录的 .env 并把变量注入容器。

四、健康检查与启动顺序

depends_on 只解决顺序,解决不了「数据库还没就绪 web 就连接失败」的问题。配合健康检查才能做到真正等就绪:

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

更稳妥的写法是在 web 的 depends_on 里加 condition: service_healthy,让 Compose 等 db 的健康检查通过后再启动 web。数据库容器一般自带 pg_isready,也可以作为健康检查命令。

五、常用命令

命令 作用
docker compose up -d 后台启动
docker compose down 停止并移除容器和网络
docker compose logs -f 实时查看日志
docker compose build 构建镜像
docker compose exec web sh 进入容器执行命令
docker compose restart web 重启单个服务

down 不会删除命名卷,所以数据库数据默认还在;真要彻底清理数据用 docker compose down -v,用之前确认没有要保留的东西。

六、生产最佳实践

  1. 使用 .env 文件管理环境变量,敏感值用密钥管理工具注入。
  2. 配置资源限制(CPU/内存),防止某个容器把整台机器吃满。
  3. 设置 restart: unless-stopped 策略,服务器重启后服务自动拉起。
  4. 使用命名卷持久化数据,别用容器内的临时目录存状态。
  5. 不要把敏感信息硬编码进镜像或 Compose 文件,用 secret 或环境变量注入。

一个完整案例

一个电商后台由 Laravel 应用、MySQL、Redis 和队列 worker 组成。用 Compose 定义四个服务,本地 docker compose up 一条命令全部起来,开发体验接近一键;生产环境用叠加的 prod 文件,MySQL 数据落在命名卷里,worker 和 web 共用同一份代码镜像。新同事加入,克隆仓库后 docker compose up 就能开始开发,省掉了「环境装不起来」这个最常见的 onboarding 阻碍。这套方案还有一个附带好处:因为整个栈的依赖、端口、数据卷都写在 Compose 文件里,代码评审时能直接看到环境定义,新功能要加一个服务(比如加一个 Elasticsearch),改几行 YAML 就能让所有人都拿到一致的环境。

常见问题

  • 容器之间连不上? 检查是否用了服务名而不是 localhost,以及两个服务是否在同一个网络里。
  • 端口冲突? 宿主机端口被占时,改 ports 左侧的宿主端口即可,容器内端口保持不变。
  • 重启后数据没了? 看看数据是否写进了容器可写层而不是命名卷,容器删除后可写层会一起消失。
  • 数据库连不上、服务秒退? 大概率是数据库还没就绪,web 就先启动了。给 db 加 healthcheck,并在 web 的 depends_on 里配置 condition: service_healthy。

参考:Docker Compose 官方文档 https://docs.docker.com/compose/ ;Compose 规范 https://compose-spec.io/