跳转至

Docker Compose — 声明式地运行多服务应用

这一章解决一个已经在眼前的问题: 一个应用两个服务(数据库 + 管理工具), 就要开两个终端敲两条长命令;服务一多,重来一遍 = 灾难。 主线例子:一个 PostgreSQL + pgAdmin 的双服务应用,从 compose.yml 的 24 行出发, 走完"起、扩、拆、多环境、模块化"的全程。

1. 先立概念:声明式 vs 命令式

书里用一段对话式的讲解立了全章的地基1:

  • 命令式(imperative):一步步告诉系统该做什么、每一步怎么做、 每种意外怎么处理——必须显式、精确、覆盖所有边缘情况;
  • 声明式(declarative):只告诉系统期望的状态(我想要什么), 由系统自己琢磨怎么达成、偏离了怎么拉回来。

Docker 明确推荐声明式路线,Compose 就是这条路的工具1。 第 09 章说过"编排 = 调和期望状态",Compose 是这套思想在单机上的第一次完整体验。

2. 主线例子:24 行的 compose.yml

应用的形态:db 服务(PostgreSQL,镜像 postgres:alpine)+ pgadmin 服务 (pgAdmin 4,镜像 dpage/pgadmin4)。compose.yml 的结构(照书里的逐行讲解)2:

services:
  db:
    image: postgres:alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: pets
    volumes:
      - pg-data:/var/lib/postgresql/data        # 数据持久化
      - ./db:/docker-entrypoint-initdb.d        # 首次启动跑初始化脚本
  pgadmin:
    image: dpage/pgadmin4
    ports:
      - 5050:80                                # 宿主 5050 → 容器 80
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@acme.com
      PGADMIN_DEFAULT_PASSWORD: admin
    volumes:
      - pgadmin-data:/var/lib/pgadmin
volumes:
  pg-data:
  pgadmin-data:

四件事要看懂2:

  1. services 块:每个服务一个条目;image 指定镜像,environment 注入环境变量, ports 发布端口,volumes 挂卷(命名卷持久化数据,宿主目录可以塞初始化脚本);
  2. 顶层 volumes 块:服务用到的命名卷必须在这里声明;首次运行创建,之后复用—— 数据库崩了重启,数据还在;
  3. docker compose up 一条命令:拉镜像、自动建一个叫 step1_default 的网络、 建两个卷、起两个容器——名字全部带 目录名前缀(step1_db-1、step1-pg-data……)。 这个前缀就是"项目名",可用 --project-name demo 显式指定; 项目机制把网络、容器、卷分组隔离3;
  4. 服务名就是主机名:pgAdmin 里连数据库,地址栏填 db 即可—— Compose 自动建的用户自定义网络内置 DNS(第 10 章的彩蛋在这里兑现)2。

起好以后浏览器开 localhost:5050,用 compose.yml 里定义的邮箱和密码登录, 就能管理数据库了2。

3. 用 Compose 构建和推送

compose.yml 里可以写 build: 段(context + dockerfile),于是:

  • docker compose build——只构建所有镜像;
  • docker compose up -d --build——构建并后台启动;
  • 构建出的镜像自动命名,如 <用户名>/ch11-web:1.0;
  • docker compose push——把定义的所有镜像推上注册表(前提:docker login 过)4。

开发循环因此收成一条命令:改完代码,up -d --build。

4. 扩容:一条命令,一个坑

应用火了,web 服务要加实例(水平扩展,scale out):

  $ docker compose up -d --scale web=3
    → web-1、web-2、web-3 三个容器
    → 但!实例 2、3 起不来:宿主端口 3000 已被实例 1 占用[^5]

坑的解法:ports 里只写容器端口、不写宿主端口(- 3000 而不是 3000:3000),Docker 就为每个实例挑一个临时端口(书里实测分配到 50162、50165、 50166),docker compose ps 里能看到各自映射,curl 逐个验证都能工作5。

这个坑是 Compose 扩容的边界预告:同一服务的多个实例共享不了同一个宿主端口, 要上真正的负载均衡(第 09 章那个外部服务),单机 Compose 需要你在前面再架一层。

5. 多环境:overrides

同一份应用,开发/测试/生产配置不同。做法:基础文件 + 覆盖文件:

  $ docker compose -f compose.yml -f compose.prod.yml up
    ↑ 后文件的字段覆盖/合并前文件同名服务的字段[^6]

命名约定:基础叫 compose.yml、覆盖叫 compose.override.yml 时, docker compose up 会自动叠加,不用写 -f;其他任何文件名都要显式 -f。 服务多、环境多时 override 链会变笨重——这时该用 include6。

6. 模块化:include(Compose v2.20+)

大项目里一个文件涨到没法维护,团队希望"服务定义挨着它自己的代码"。 include 是正式的解法(注意书里强调的两个词:)7

include:
  - ../services/db/compose.yaml
  - ../services/web/compose.yaml
  • include ≠ overrides:overrides 是合并/覆盖同名服务的字段; include 是整份文件拉进来,而且同名资源直接报错,绝不静默合并;
  • 每个被 include 的文件在自己的项目目录里求值——build context、卷挂载、 env_file 的相对路径都按"它自己单独跑"的样子解析;
  • 长语法可以指定 project_directory 和 env_file,控制得更细;
  • include 可以递归嵌套(被拉进来的文件还能再 include 别的)—— monorepo 里每个团队拥有自己的 compose 文件,顶层一份文件 include 全部, 一条 docker compose up 起整个栈7。

7. Compose 的边界:什么时候必须上编排器

书里给了对照(要点转述)8:

维度 Docker Compose 完整编排器(Kubernetes/Swarm)
主机 单机 跨多机
扩容 手动 --scale,受单机和端口限制 声明式自动扩缩,跨节点调度
自愈 无(容器退出不会自动找别处重建) 控制循环持续调和:挂了自动补、节点死了自动挪
服务发现/负载均衡 内置 DNS(单机够用) 集群级 DNS + 虚拟 IP 负载均衡
适用 开发环境、CI、小型单机部署 生产、多节点、高可用要求

一句话:Compose 是"一台机器上的多容器管家",不是编排器—— 第 09 章那十二个模式里它只实现了服务发现一个半。这也正好回答了 "什么时候读第 14 章":需要跨机自愈那天。

8. 作者的判断与证据

  • "声明式优于命令式"是 Docker 官方立场,书里照单全收,依据是命令式 必须穷举边缘情况、天然不可扩展1。
  • include 与 overrides 的区分(报错 vs 静默合并)是硬规则,不是风格偏好7。
  • "Compose ≠ 编排器"的边界判断与全书结构一致(第 14 章才讲编排), 对照表是其论证8。
  • 扩容的端口冲突是实测坑(带具体端口号),证据硬5。

判断(我们的,不是书里的): Compose 在 2026 年的真实角色已经从 "开发工具"外溢到"小型生产"——单机 VPS 上跑个小站,Compose + 定时备份 是完全合格的生产形态,不必为了上 K8s 而上 K8s。书里"生产必须编排器"的 暗示可以放宽为:多节点、高可用、频繁发布三者占齐才必须。 如果错,会错在: 对合规/审计要求高的企业环境,即便单机也更倾向托管编排 (拿现成的 RBAC、审计日志、策略引擎),这时候 Compose 的朴素反而是负担。

9. 边界与局限

  • 本章示例以 pgAdmin 网页操作为主,UI 截图不可搬运;命令与 YAML 是可靠部分。
  • --scale 不做负载均衡,书里没给"前面架一层"的完整方案(那是第 10 章 Traefik 手艺的用武之地)。
  • include 是 v2.20(2023 年中)的新特性,旧版 Compose 只有 -f 链; 书上写的是"较新版本",实际部署前核对 docker compose version。
  • Compose 的 secrets、healthcheck 依赖、profiles(按 profile 启停服务组) 等高级特性本章未展开。

10. 可带走的

  1. 声明式 = 描述期望状态;Compose 是 Docker 官方推荐的声明式路线1。
  2. compose.yml 四要素:services(image/environment/ports/volumes)+ 顶层 volumes 声明2。
  3. 项目名 = 目录名前缀,资源分组隔离;--project-name 可改3。
  4. 服务名即主机名——Compose 网络内置 DNS 开箱即用2。
  5. 开发循环一条命令:docker compose up -d --build;推送:docker compose push4。
  6. 扩容只写容器端口(不写宿主端口),让 Docker 分临时端口,否则端口冲突5。
  7. 多环境:覆盖文件,compose.override.yml 有命名约定;其他名字要 -f6。
  8. include ≠ overrides:整份拉入、同名报错、路径各自求值、可递归——模块化正解7。
  9. Compose 管单机,编排器管集群;自愈、跨机调度、自动扩缩是分水岭8。

11. 原文地图

主题 原书章 原文位置
声明式 vs 命令式 What has changed since the last edition of the book text/120-fm-what-has-changed-since-the-last-edition-of-the-b.txt:1(搜「Declarative」) · text/120-fm-what-has-changed-since-the-last-edition-of-the-b.txt:11(搜「desired state」)
compose.yml 逐行讲解 Running a multi-service app text/121-fm-running-a-multi-service-app.txt:7(搜「declarative」) · text/121-fm-running-a-multi-service-app.txt:21(搜「services」) · text/121-fm-running-a-multi-service-app.txt:29(搜「image」) · text/121-fm-running-a-multi-service-app.txt:37(搜「pg-data」) · text/121-fm-running-a-multi-service-app.txt:61(搜「volumes」)
up 的产出与项目前缀 Running a multi-service app text/121-fm-running-a-multi-service-app.txt:83(搜「compose up」) · text/121-fm-running-a-multi-service-app.txt:92(搜「step1_default」) · text/121-fm-running-a-multi-service-app.txt:99(搜「prefix」)
服务名当主机名 Running a multi-service app text/121-fm-running-a-multi-service-app.txt:139(搜「corresponds to the name」)
构建与推送 Building images with Docker Compose text/123-fm-running-an-application-with-docker-compose.txt:7(搜「ch11-web」) · text/126-fm-using-docker-compose-overrides.txt:3(搜「compose push」)
--scale 与端口冲突 Scaling a service text/124-fm-scaling-a-service.txt:34(搜「--scale web=3」) · text/124-fm-scaling-a-service.txt:42(搜「same host port」) · text/124-fm-scaling-a-service.txt:77(搜「3000」) · text/124-fm-scaling-a-service.txt:95(搜「50162」)
项目名与卷前缀 Scaling a service text/124-fm-scaling-a-service.txt:10(搜「prefix」) · text/124-fm-scaling-a-service.txt:17(搜「project-name」)
overrides 约定 Using Docker Compose overrides text/126-fm-using-docker-compose-overrides.txt:25(搜「override」) · text/127-fm-modularizing-applications-with-include.txt:3(搜「compose.override.yml」) · text/127-fm-modularizing-applications-with-include.txt:7(搜「-f <base」)
include 语义 Modularizing applications with include text/127-fm-modularizing-applications-with-include.txt:23(搜「include ≠ overrides」) · text/127-fm-modularizing-applications-with-include.txt:26(搜「v2.20」) · text/127-fm-modularizing-applications-with-include.txt:29(搜「project directory」) · text/127-fm-modularizing-applications-with-include.txt:52(搜「error」) · text/127-fm-modularizing-applications-with-include.txt:54(搜「recursively」)
Compose vs 编排 When to use Docker Compose versus a full orchestration system text/128-fm-when-to-use-docker-compose-versus-a-full-orchest.txt:1(搜「conflicts」) · text/129-fm-summary.txt:6(搜「Scaling」)
章小结 Summary text/129-fm-summary.txt:5(搜「declarative」)

  1. 出处:「What has changed since the last edition of the book」(text/120-fm-what-has-changed-since-the-last-edition-of-the-b.txt:1,搜「Declarative」;:11,搜「desired state」;:7,搜「declarative approach」)。 ↩↩↩↩

  2. 出处:「Running a multi-service app」第 1–67 段(text/121-fm-running-a-multi-service-app.txt:7,搜「declarative way」;:21,搜「services」;:29,搜「image」;:37,搜「pg-data」;:40,搜「docker-entrypoint-initdb.d」;:61,搜「volumes」;:139,搜「corresponds to the name」)。 ↩↩↩↩↩↩

  3. 出处:「Running a multi-service app」第 80–119 段(text/121-fm-running-a-multi-service-app.txt:83,搜「compose up」;:91,搜「step1_default」;:94,搜「step1_pgadmin-data」;:99,搜「prefix」)。 ↩↩

  4. 出处:「Building images with Docker Compose」(text/123-fm-running-an-application-with-docker-compose.txt:7,搜「ch11-web」;:8,搜「node:20-」)与「Building and pushing an application」(text/126-fm-using-docker-compose-overrides.txt:3,搜「compose push」)。注:部分小节正文因转码顺序错位,锚取相应文件中的真实句。 ↩↩

  5. 出处:「Scaling a service」第 24–107 段(text/124-fm-scaling-a-service.txt:34,搜「--scale web=3」;:36,搜「web-1」;:42,搜「same host port」;:47,搜「leave out the host port」;:95,搜「50162」;:100,搜「curl -4」)。 ↩↩↩

  6. 出处:「Using Docker Compose overrides」(text/126-fm-using-docker-compose-overrides.txt:25,搜「override」;text/127-fm-modularizing-applications-with-include.txt:3,搜「compose.override.yml」;:7,搜「-f <base compose file>」)。 ↩↩

  7. 出处:「Modularizing applications with include」第 15–56 段(text/127-fm-modularizing-applications-with-include.txt:16,搜「cumbersome」;:23,搜「include ≠ overrides」;:26,搜「v2.20」;:29,搜「project directory」;:51,搜「error」;:54,搜「recursively」)。 ↩↩↩↩

  8. 出处:「When to use Docker Compose versus a full orchestration system」(text/128-fm-when-to-use-docker-compose-versus-a-full-orchest.txt:1,搜「conflicts」)与「Summary」的对照表(text/129-fm-summary.txt:6,搜「Scaling」;:5,搜「Declarative」)。 ↩↩↩