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: 2,docker 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,用之前确认没有要保留的东西。
六、生产最佳实践
- 使用
.env文件管理环境变量,敏感值用密钥管理工具注入。 - 配置资源限制(CPU/内存),防止某个容器把整台机器吃满。
- 设置
restart: unless-stopped策略,服务器重启后服务自动拉起。 - 使用命名卷持久化数据,别用容器内的临时目录存状态。
- 不要把敏感信息硬编码进镜像或 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/