当你用 Docker 跑起第一个容器后会很快发现:真实应用从来不是单个容器,而是「Web 服务 + 数据库 + 缓存 + 反向代理」的组合。手动 docker run 一堆参数既容易出错又无法复现,Docker Compose 正是为解决「多容器编排」而生的官方工具——它用一份 docker-compose.yml 声明所有服务、网络与卷,一条命令就能拉起整套环境。如果你还停留在单容器阶段,建议先读完 Docker 基础实战 再来看本文。
一、Compose 到底解决了什么问题
单条 docker run 只能管一个容器。当项目里有 Spring Boot、MySQL、Redis、Nginx 四个服务时,你需要记住四组端口映射、环境变量、挂载目录和启动顺序,换台机器或交给同事就必然「在我电脑能跑」。一个真实的踩坑场景是:凌晨线上数据库容器被误 docker rm 删掉,因为没人记得那条带 -v 的启动命令,数据卷随之消失。Compose 把这些声明收敛进一个版本化的 YAML 文件,配合 docker compose up -d 一键拉起、docker compose down 一键清理,环境与代码一并进 Git,彻底消灭「配置漂移」,也让新同事 git clone 后一条命令就能还原整套环境。
顺便提醒:Docker 官方自 v2 起已将 Compose 改为 Docker CLI 的子命令(docker compose),旧版的独立二进制 docker-compose(带连字符)已进入维护模式。两者命令几乎一致,新项目请直接用 docker compose,避免在生产机上因版本不一致踩坑。
二、核心字段速查表
| 字段 | 作用 | 示例 |
|---|---|---|
| services | 定义每个容器服务 | web / db / redis |
| image | 使用现成镜像 | mysql:8.0 |
| build | 基于 Dockerfile 构建 | build: ./app |
| ports | 端口映射 | “8080:8080” |
| environment | 注入环境变量 | MYSQL_ROOT_PASSWORD |
| volumes | 挂载数据卷 | db_data:/var/lib/mysql |
| depends_on | 控制启动顺序 | – db – redis |
| networks | 自定义网络 | backend / frontend |
| healthcheck | 容器健康探测 | mysqladmin ping |
| profiles | 按需启用服务组 | profile: debug |
三、第一个编排:Web + Redis + MySQL
下面是一个最小可跑的 docker-compose.yml,它定义了三个服务并让它们处于同一默认网络,Web 可直接用服务名 db、redis 互访(Compose 内置 DNS)。
services:
web:
build: ./app
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/demo
SPRING_REDIS_HOST: redis
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: demo
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
retries: 5
volumes:
db_data:
3.1 网络与端口
Compose 会为每个项目创建一个默认 bridge 网络,服务间用「服务名」即主机名互通,无需暴露数据库端口到宿主机。只有真正需要对外访问的 web 才做 ports 映射;若想套一层反代,可参考 Nginx 反向代理实战 把 8080 收口到 80/443。
3.2 启动与验证
# 后台拉起整套服务
docker compose up -d
# 查看容器状态(含健康)
docker compose ps
# 查看某个服务日志
docker compose logs -f web
# 停止并移除容器(保留数据卷)
docker compose down
3.3 加一层 Nginx 反代
把 Web 藏到 Nginx 后面,对外只暴露 80/443,是常见生产形态。注意 web 不再做宿主机端口映射,而是仅在内部网络对 Nginx 可见:
services:
nginx:
image: nginx:1.27-alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- web
web:
build: ./app
expose:
- "8080"
environment:
SPRING_REDIS_HOST: redis
3.4 配套的最小 Dockerfile
上面的 web 用 build: ./app 构建,意味着 ./app 目录下要有一份 Dockerfile。一个面向 Spring Boot 的可复现示例:
# ./app/Dockerfile
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY target/demo-0.0.1.jar app.jar
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
把 Dockerfile 与 docker-compose.yml 一起提交,别人拿到仓库就能 docker compose up -d --build 直接跑起来,无需任何口头交接。这也是 Compose 作为「团队环境契约」的价值所在。
四、数据持久化:volumes 与 bind mount
容器文件系统是易失的,数据库数据必须落到卷上。命名卷(named volume)由 Docker 管理、性能更好;bind mount 直接挂宿主机目录,便于本地改代码热加载。生产务必用命名卷,避免把宿主机任意目录直接挂进容器造成权限混乱。
| 方式 | 写法 | 适用场景 |
|---|---|---|
| 命名卷 | db_data:/var/lib/mysql | 生产数据库、需持久化 |
| bind mount | ./src:/app/src | 本地开发热更新 |
| tmpfs | tmpfs: /tmp | 敏感/临时文件 |
services:
db:
image: mysql:8.0
volumes:
- db_data:/var/lib/mysql # 命名卷,数据存活于容器之外
volumes:
db_data:
五、环境变量与多环境
把密码、端口等抽到 .env,再在 YAML 中用 ${VAR} 引用,可避免敏感信息进仓库。多环境则用 docker-compose.override.yml 叠加开发配置(如挂载源码、开调试端口),生产环境用 -f docker-compose.yml -f docker-compose.prod.yml 组合,保持基础定义不变。
# .env
MYSQL_ROOT_PASSWORD=rootpass
APP_PORT=8080
# docker-compose.yml 中引用
services:
web:
ports:
- "${APP_PORT}:8080"
六、生产环境注意事项
本地能跑不等于生产稳。务必加上 restart: unless-stopped 让进程崩溃后自拉起;用带 condition: service_healthy 的 depends_on 让依赖方真正等数据库就绪而非仅等容器启动;给每个服务设 mem_limit 防止单容器吃满内存拖垮整机;数据库端口不要 bind 到 0.0.0.0,只在内部网络互通。还可把这套栈接到 Prometheus+Grafana 监控实战 采集容器指标,配合告警及时发现异常。
services:
web:
restart: unless-stopped
mem_limit: 512m
read_only: true
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
七、Compose 与 Kubernetes 的取舍
Compose 适合单机、中小规模、开发到中小生产;当你需要多节点调度、自动扩缩容、滚动更新时,就该上 K8s。两者并非替代关系——很多团队用 Compose 做本地与生产轻量部署,再在规模扩张时迁移到 Kubernetes 入门 体系。
| 维度 | Docker Compose | Kubernetes |
|---|---|---|
| 部署目标 | 单机 | 多节点集群 |
| 学习成本 | 低(一个 YAML) | 高 |
| 扩缩容 | 手动/scale | 自动 HPA |
| 滚动更新 | 有限 | 原生支持 |
| 适用规模 | ≤ 数台 | 数十到数千台 |
八、常见排障清单
排障是 Compose 的实战重点,以下是高频坑:
- 端口冲突:
docker compose up报port is already allocated,用docker ps找出占用进程或改映射端口。 - 卷权限:MySQL/Redis 报
permission denied,多是宿主机目录属主与容器内用户不一致,优先改用命名卷而非 bind mount。 - depends_on 不等就绪:容器起来了应用却连不上库,必须用
condition: service_healthy而非裸depends_on。 - .env 未加载:变量为空,确认
.env与 compose 文件同目录,且未被.gitignore误删。 - 配置校验:
docker compose config能在启动前暴露缩进与字段拼写错误。
九、常用命令速查
docker compose config # 校验并展开最终配置
docker compose build # 重新构建镜像
docker compose up -d --build
docker compose exec db bash # 进入容器
docker compose top # 查看进程
docker compose down -v # 连卷一起删(慎用)
docker compose profiles # 列出可用 profiles
十、用 –scale 做水平扩展
当单实例 Web 扛不住流量时,Compose 也能快速横向扩容(前提是应用无状态、会话外置到 Redis 或数据库)。一条命令起三个 Web 实例:
# 把 web 服务扩到 3 个副本
docker compose up -d --scale web=3
# 查看已起的副本
docker compose ps
注意 --scale 要求 web 不能写死固定宿主机端口(否则副本间端口冲突),应只 expose 内部端口,再靠前面的 Nginx 做负载均衡。这种「无状态 + 反代」的组合,正是从 Compose 平滑过渡到 K8s Deployment 的前奏——理念完全一致,只是调度器换成了集群。
十一、总结
Docker Compose 把「多容器应用」从一串易忘的命令变成一份可版本化、可复现的声明文件。掌握 services/volumes/networks/healthcheck 四个核心,再配合环境变量、多文件叠加与生产级配置,你就能用极小成本管好自己的服务栈。CI 环节可用 GitHub Actions 实战 自动构建镜像并推送,形成从代码到部署的闭环;流量上来后用 --scale 横向扩容,规模再大都可平滑迁移到 K8s。它不万能,却是中小团队最划算的第一块运维基石。




