05 篇解决了“一个怎么运行”,06 / 07 篇解决了“版本怎么构建、推送和触发”。但开发侧引入微服务后,一个应用往往会拆成网关、前端、API、数据库、缓存和消息队列,运维对象从 1 个变成多个。
逐条 docker run 仍然可以把它们启动起来,却会重新变成一套靠记忆维护的运维琐事:网络、端口、卷、环境变量、依赖顺序和重启策略散落在命令参数里。真正需要管理的不是“多敲几条命令”,而是多个服务如何组成一个应用。
这就是 Docker Compose:把应用的服务拓扑和期望状态写进 compose.yaml,随源码一起提交、Review 和。它和 Jenkinsfile / GitHub Actions 是同一套基础设施即代码思想的不同落点:
| Jenkinsfile / Actions YAML | compose.yaml | |
|---|---|---|
| 声明的是 | 怎么构建与部署(动作链) | 跑哪些服务、如何互联(期望状态) |
| 写在哪 | 代码仓库 | 代码仓库 |
| 谁执行 | 引擎 | docker compose CLI |
| 思想 | 运维左移 / IaC | 运维左移 / IaC |
对绝大多数开源项目来说,Compose 也是默认的自部署入口之一:项目把 compose.yaml、环境变量模板和启动说明随源码发布,用户只需准备 Docker,再按文档执行 docker compose up -d。因此 Compose 文件不是临时脚本,而是项目交付物的一部分。
从「一个容器」到「一个应用」
回顾 05 的最小闭环:
docker pull nginx:1.27-alpine
docker run -d --name my-nginx -p 80:80 nginx:1.27-alpine真实项目通常不止一个进程。以「前端静态站 + 后端 API + 数据库」为例:
| 服务 | 作用 | 典型端口 |
|---|---|---|
| Nginx | 公网入口、、 API | 80 / 443 |
| 应用( / Flask) | 业务 API | 8080(内部) |
| PostgreSQL | 持久化数据 | 5432(内部) |
用命令式写法,你要自己管网络、端口、卷、依赖顺序:
docker network create app-net
docker run -d --name db --network app-net \
-e POSTGRES_PASSWORD=secret \
-v pgdata:/var/lib/postgresql/data \
postgres:16-alpine
docker run -d --name api --network app-net \
-e DB_URL=jdbc:postgresql://db:5432/app \
your-registry/your-api:1.0.0
docker run -d --name gateway --network app-net \
-p 80:80 \
-v ./nginx.conf:/etc/nginx/conf.d/default.conf:ro \
nginx:1.27-alpine再加监控(Prometheus / Grafana / Loki)就是 6~8 条命令。问题立刻暴露:
- 参数散落:漏一个
-v/-e就启动失败,且难复现 - 顺序靠人记:API 依赖 DB 就绪,谁保证?
- 清理靠人记:
stop+rm漏一个就成孤儿容器 - 无法版本管理:一坨 shell,没法 Code Review
这和 02 篇「手动备份像错题本」、05 篇「服务器变脏」是同一类运维痛点——动作靠记忆,状态不可声明。
Compose 的本质:声明应用拓扑与期望状态
docker run 是命令式:你告诉系统每一步怎么做。
docker-compose.yml 是声明式:你描述期望状态,由 Compose 把实际状态收敛过去。
可以把几种工具的职责分开理解:
| 层次 | 解决的问题 |
|---|---|
| Docker Image | 服务运行什么 |
| Container | 服务运行实例 |
| Docker Compose | 多个服务如何组成一个应用 |
| Jenkinsfile / GitHub Actions | 什么时候构建、推送和部署 |
| Kubernetes | 多机调度、弹性扩缩和故障自愈 |
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
api:
image: your-registry/your-api:1.0.0
environment:
DB_URL: jdbc:postgresql://db:5432/app
depends_on:
- db
gateway:
image: nginx:1.27-alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
volumes:
pgdata:日常只需要:
docker compose up -d # 创建网络、拉镜像、按依赖启动
docker compose ps # 看状态
docker compose logs -f # 看日志
docker compose down # 停掉并清理容器与网络(卷默认保留)depends_on 默认只保证启动顺序,不保证数据库已经可以接受连接。生产配置应为依赖服务增加 healthcheck,并让应用自身具备连接重试能力;容器显示 Up,不代表应用已经真正就绪。
对比:
| 维度 | 手动 docker run | Docker Compose |
|---|---|---|
| 配置管理 | 散落在命令行 | YAML 进 Git,可追溯 |
| 启动方式 | 逐个敲,记依赖顺序 | 一条命令,depends_on 声明顺序 |
| 环境一致性 | 每次手敲易错 | 同一份 YAML 本地 / 测试 / 生产同构 |
| 停止清理 | 逐个 stop + rm | docker compose down |
| 团队协作 | 靠文档或口口相传 | clone + compose up 即可复现 |
Compose 文件通常命名为 compose.yaml;docker-compose.yml 是历史命名,现代 Docker Compose 仍兼容它。
本站级示例:多服务可观测性栈
上面是「应用三件套」的最小模型。本仓库实际用 Compose 编排了网关、站点与可观测性套件(示意结构):
| 服务 | 作用 |
|---|---|
| Nginx 网关 | 反向代理、入口 |
| 站点 | 文档站容器 |
| Prometheus / Grafana | 指标与面板 |
| Loki + Promtail | 日志采集与存储 |
完整 YAML 可随项目仓库版本管理——新人不必背命令,只需读文件。这正是 07 篇说的「运维左移」:上线拓扑从某个人脑子里,变成仓库里的显性代码。
生产密钥不要写进 YAML 明文。用
.env(加入.gitignore)或密钥管理注入;YAML 里只写${POSTGRES_PASSWORD}这类引用。
Compose 如何把流水线打薄
Compose 不替代流水线,而是把“容器应用如何部署”从流水线脚本中抽离出来,成为流水线调用的部署动作载体:
git push
→ CI 构建镜像并推仓库(06 / 07)
→ CD 在服务器上:git pull(拿到最新 compose)→ docker compose pull → docker compose up -d服务器仍然可以只装 Docker——不装 mvn / pip,由 Dockerfile 和 Compose 在服务器上完成构建。这就是 Build in Server:适合个人项目、开源项目和单机小规模部署,源码到服务器后执行 docker compose up --build -d 即可。规模扩大后,再切换为 CI 构建镜像、服务器只执行 pull + up,以减少生产机资源消耗并提高构建可追溯性。
因此,流水线有两种合理形态:Build in Server 模式下,流水线主要负责更新源码、触发 docker compose up --build 和验证结果;CI Build 模式下,流水线负责构建并推送镜像,服务器只执行 docker compose pull 和 docker compose up -d。两种模式都由 Compose 负责服务拓扑和生命周期,差别在于镜像在哪里构建。
Build in Server 的代价也需要明确:构建会消耗生产机的 CPU、内存和磁盘,构建结果还可能受服务器环境影响;当需要多环境复用、构建审计、快速回滚或减少生产资源争抢时,应切换到 CI Build。
部署前建议先检查最终配置,再执行变更:
docker compose config
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100单机多服务部署的终点与边界
| 场景 | 建议 |
|---|---|
| 个人 / 中小项目,单机部署或多台机器分别独立部署 | Compose 足够——单机多服务部署的终点 |
| 数十服务、多节点、弹性扩缩 | 再考虑 K3s / Kubernetes |
| 多区域容灾、灰度发布 | Service Mesh / GitOps(进阶篇) |
掌握 Compose + 前面 01–07,已经能覆盖绝大多数中小型项目的单机部署工作。 这也是基础篇把 Compose 放在最后一篇的原因:它把“ + 声明式 + 多服务”收成一个可落地的闭环。Compose 是单机、多容器应用的部署终点,但不是多机集群的调度方案。
小结
Docker Compose 把 05 的单容器能力扩展为多服务期望状态:一份 YAML、一条 up、一键 down。它与 Jenkinsfile / Actions YAML 同属 IaC——区别只是声明对象不同(拓扑 vs 动作链)。
下一步进入 第 09 篇 · 基础篇总结:回顾 01–08 的范式跃迁、能力清单,以及「够用」与「还差得远」的边界。
思考
docker-compose.yml应该提交到 Git 吗?里面有数据库密码时怎么处理?depends_on能保证依赖服务「已就绪」吗?MySQL 容器Up了但还在初始化,应用连库会怎样?- 单机小项目为什么可以选择在生产机执行
docker compose up --build?当构建耗时、服务器资源或团队规模上升后,什么时候应该切换到「CI 构建镜像 + 服务器compose pull && up」?