第六章: 终结繁琐:使用 Docker Compose 编排多容器应用

第六章: 终结繁琐:使用 Docker Compose 编排多容器应用

摘要: 在本章,我们将彻底告别那些繁琐、冗长且需要手动按顺序执行的 docker 命令。我们将学习使用 Docker Compose——现代容器化应用编排的基石——来让我们用一份独立的、声明式的 docker-compose.yml 配置文件,描述管理 我们整个前后端分离的应用。本章结束后,您将能通过 docker compose updocker compose down 两个简单的命令,实现整个应用环境的一键启动与销毁。

本章的起点:回顾第五章的“痛苦”

在深入 Docker Compose 之前,让我们清晰地回顾一下,在上一章为了让我们的前后端应用跑起来,我们手动执行了多少步骤:Z

  1. docker network create my-app-network (手动创建网络)
  2. docker volume create my-api-data (手动创建数据卷)
  3. docker run -d --name backend-api --network ... -v ... my-api:1.1 (手动启动后端,并挂载网络和数据卷)
  4. docker run -d --name frontend --network ... -p ... my-react-app:production-v1.0 (手动启动前端,并挂载网络和端口)
  5. docker stop backend-api frontend && docker rm backend-api frontend (手动清理)

这个过程不仅繁琐,而且极易出错。任何一个参数的遗漏或错误,都可能导致应用无法正常工作。Docker Compose 的诞生,正是为了将我们从这种指令式的、过程化的操作中解放出来。


6.1. 初识 docker-compose.yml:将命令翻译为配置

承上启下: 我们将学习如何将上一章那些冗长的 docker run 命令,逐字逐句地“翻译”成 docker-compose.yml 文件中的配置项。这将是我们从“命令式”操作转向“声明式”管理的第一次实践。

第一步:创建 docker-compose.yml 文件

在您项目的根目录下(即包含 my-react-appbackend-api 两个子目录的地方),创建一个名为 docker-compose.yml 的新文件。这个 YAML 文件将是我们应用所有服务的“总纲”。

第二步:将后端服务“翻译”为 Compose 配置

我们的目标是翻译这条命令:
docker run -d --name backend-api --network my-app-network -v my-api-data:/app/data my-api:1.1

我们在 docker-compose.yml 中这样描述它:

1
2
3
4
5
6
# docker-compose.yml (v0.1 - 初始翻译)

services: # 所有需要独立运行的服务都定义在这里
backend-api: # 这是我们为服务起的内部名字,Compose会用它作为默认的主机名
container_name: backend-api # 对应 --name 参数,指定容器的实际名称
image: my-api:1.1 # 对应要使用的镜像

引入一个更佳实践:由 Compose 直接构建镜像

直接使用 image: my-api:1.1 意味着我们必须先手动 docker build 好这个镜像。但 Compose 提供了更强大的功能:它可以直接根据 Dockerfile 为我们构建镜像。

我们将 image 配置项替换为 build

1
2
3
4
5
6
# docker-compose.yml (v0.1 - 初始翻译)

services:
backend-api:
container_name: backend-api
build: ./backend-api # 告诉 Compose,请进入 ./backend-api 目录,并使用那里的 Dockerfile 来构建镜像

这比手动构建要方便得多。

第三步:将前端服务“翻译”为 Compose 配置

同样地,我们来翻译前端的启动命令:
docker run -d --name frontend --network my-app-network -p 8080:80 my-react-app:production-v1.0

将其追加到 docker-compose.yml 文件中:

1
2
3
4
5
6
7
8
9
10
11
12
# docker-compose.yml (v0.1 - 完整初稿)

services:
backend-api:
container_name: backend-api
build: ./backend-api

frontend:
container_name: frontend
build: ./my-react-app # 指向我们前端项目的目录
ports: # 对应 -p 参数
- "8080:80" # YAML 列表语法,表示 主机端口:容器端口

关于 version 字段

在旧版的 docker-compose.yml 文件中,你通常会在文件顶部看到一个 version: '3.8' 这样的字段。在新版的 Docker Compose 中,这个字段已经变为可选。Compose 会根据你使用的 YAML 关键字自动推断文件格式。为保持简洁,我们遵循现代规范,省略该字段。


第四步:第一次运行

我们已经有了一个初步的 docker-compose.yml 文件。现在,我们不再需要 docker run 了。在 docker-compose.yml 所在的根目录下,执行:

1
docker compose up

您会看到:

  • Docker Compose 首先并行 构建backend-apifrontend 两个服务的镜像。
  • 然后,它 启动 了两个容器。
  • 由于我们没有加 -d 参数,所有服务的日志会混合输出到当前终端,这对于初次启动和调试非常有用。

现在,你可以访问 http://localhost:8080,前端页面应该可以正常显示。

第五步:识别缺失的环节并清理

虽然服务启动了,但我们的 v0.1 版本还存在明显的问题:

  1. 我们还没有定义 my-app-network,所以前后端容器之间依然无法通信。
  2. 我们还没有定义 my-api-data 数据卷,所以后端的数据依然是易失的。

在终端中按下 Ctrl+C,Compose 会优雅地停止所有服务。然后,执行以下命令来彻底清理本次启动创建的所有资源(容器、默认网络等):

1
docker compose down

我们已经成功地将启动命令转化为了配置文件,并体验了 docker compose 的基本工作流程。在下一节,我们将把网络和数据卷的定义也加入进来,构建一个完整的多服务应用。


6.2. 协同工作:在 Compose 中定义 networksvolumes

承上启下: 在上一节,我们成功地将 docker run 命令的基本操作“翻译”成了 docker-compose.yml 中的 services 配置。然而,我们的应用目前还处于“残缺”状态:前后端服务之间无法通信,后端的数据也无法持久化。现在,我们将把在第五章手动创建的 networkvolume 也纳入 Compose 的管理体系,构建一个功能完整的应用栈。

第一步:在 Compose 文件中声明“共享资源”

docker-compose.yml 不仅能定义 services,还能在顶层定义我们整个应用栈所需的共享资源,例如网络和数据卷。

请用以下内容 覆盖 你现有的 docker-compose.yml 文件。我们在 services同级,新增了 networksvolumes 两个顶层关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# docker-compose.yml (v0.2 - 添加网络与数据卷)

services:
backend-api:
container_name: backend-api
build: ./backend-api
# 新增内容:将此服务连接到下方定义的网络
networks:
- my-app-network
# 新增内容:将下方定义的数据卷挂载到此服务
volumes:
- my-api-data:/app/data

frontend:
container_name: frontend
build: ./my-react-app
ports:
- "8080:80"
# 新增内容:同样将此服务连接到我们的应用网络
networks:
- my-app-network

# 新增顶层关键字:networks,用于定义我们应用所需的所有网络
networks:
my-app-network: # 我们为网络起的名字
driver: bridge # 指定使用标准的 bridge 驱动

# 新增顶层关键字:volumes,用于定义我们应用所需的所有数据卷
volumes:
my-api-data: # 我们为数据卷起的名字
driver: local # 指定使用标准的 local 驱动

代码解读:

  • 我们在文件底部使用 networksvolumes 块,像声明变量一样,声明 了我们应用需要一个名为 my-app-network 的网络和一个名为 my-api-data 的数据卷。
  • 在每个 service 的定义内部,我们通过 networksvolumes 关键字来 引用 这些已声明的资源,将服务“接入”网络或“挂上”数据卷。

第二步:“一键启动”完整应用

我们的 docker-compose.yml 文件现在已经描述了一个功能完整的应用栈。让我们来启动它。这次,我们将使用 -d 参数,让它在后台运行。

1
docker compose up -d

这就是 Compose 的魔力! 只用一个命令,Docker Compose 就为我们自动完成了所有事情:

  • 它检查到 my-app-network 网络不存在,于是 自动创建 了它。
  • 它检查到 my-api-data 数据卷不存在,于是也 自动创建 了它。
  • 它构建了两个服务的镜像(如果需要),并以正确的配置(连接了网络、挂载了数据卷)启动了两个容器。

你可能会注意到,Compose 创建的网络和数据卷名前面,被自动加上了项目目录名作为前缀(例如 my-docker-guide_my-app-network)。这是 Compose 用来隔离不同项目资源的方式,非常实用。

第三步:全面验证

  1. 验证网络通信: 我们可以使用 docker compose exec 命令,在某个服务容器内执行命令。

    1
    2
    # 在 frontend 容器内,执行 curl 命令访问 backend-api 服务
    docker compose exec frontend sh -c "curl http://backend-api:3000/api/data"

    您应该能看到后端 API 成功返回了 JSON 数据,证明了服务间的 DNS 解析和通信完全正常。

  2. 验证数据持久化:

    • 连续执行几次上面的 exec 命令,让计数器增长。
    • 现在,执行 docker compose down销毁 应用环境。
      1
      docker compose down
      该命令会停止并删除容器、网络,但 默认会保留具名数据卷,以防数据丢失。
    • 再次执行 docker compose up -d 重建 应用环境。
    • 最后,再次执行 exec 命令访问 API。
      1
      docker compose exec frontend sh -c "curl http://backend-api:3000/api/data"

    您会发现 hits 计数器在上次的基础上继续增加了!这证明了数据卷成功地在容器的“生死轮回”之间,为我们保全了数据。

如果您希望在 down 的时候,连同数据卷也一起删除,可以使用 -v 标志:docker compose down -v。请谨慎操作此命令。

我们现在已经拥有了一个健壮的、可一键部署和销毁的多服务应用定义。在下一节,我们将为这个应用引入数据库,并学习如何管理服务之间的启动依赖关系。


6.3. 管理依赖与健康:depends_onhealthcheck

承上启下: 我们的应用正在变得越来越真实。一个典型 Web 应用除了前后端,还必然包含一个数据库。现在,我们将为系统添加一个 PostgreSQL 数据库服务。这个新角色的加入,会立刻引入一个在多服务架构中至关重要的新问题:服务启动依赖。后端 API 必须在数据库完全准备好之后才能启动,否则就会因为连接失败而崩溃。

第一步:在 docker-compose.yml 中添加数据库服务

我们首先在 docker-compose.yml 文件中定义我们的新成员:一个 postgres 数据库服务。

请用以下内容 覆盖 你现有的 docker-compose.yml 文件。注意新增的 db 服务和 db-data 数据卷:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# docker-compose.yml (v0.3 - 添加数据库)

services:
db:
image: postgres:16-alpine
container_name: my-app-db
restart: always
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=appdb
volumes:
- db-data:/var/lib/postgresql/data
networks:
- my-app-network

backend-api:
container_name: backend-api
build: ./backend-api
networks:
- my-app-network
volumes:
- my-api-data:/app/data

frontend:
container_name: frontend
build: ./my-react-app
ports:
- "8080:80"
networks:
- my-app-network

networks:
my-app-network:
driver: bridge

volumes:
my-api-data:
driver: local
db-data:
driver: local

第二步:改造后端 API 并重建镜像(关键修正)

现在,我们让 backend-api 在启动时去尝试连接数据库。这需要我们安装新的 npm 依赖,并 明确地重建镜像

首先,为 backend-api 项目添加 pg 依赖库。

1
2
3
cd backend-api
npm install pg
cd ..

然后,用以下仅用于测试数据库连接的代码,覆盖 backend-api/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// backend-api/index.js (仅用于测试数据库连接)
const { Client } = require('pg');

const client = new Client({
host: 'db', // 我们使用 Compose 提供的服务名作为主机名
port: 5432,
user: 'user',
password: 'password',
database: 'appdb',
});

async function connectToDb() {
try {
await client.connect();
console.log('✅ Successfully connected to the database!');
await client.end();
} catch (err) {
console.error('❌ Failed to connect to the database:', err);
process.exit(1); // 连接失败,立即退出进程
}
}

console.log('🚀 API service starting, attempting to connect to DB...');
connectToDb();

接着,执行最关键的一步:重建后端镜像。
因为我们修改了 package.json(通过 npm install pg),旧的镜像已经过时,它内部的 node_modules 缺少 pg 模块。我们必须基于新的代码和依赖,构建一个新的镜像。docker compose build 命令就是为此而生。

1
docker compose build backend-api

这个命令会告诉 Compose:“请只重新构建 backend-api 服务的镜像”。

第三步:制造“启动失败”的场景

镜像已正确更新,现在我们可以来观察服务间的启动竞争问题了。

1
docker compose up

仔细观察前台输出的日志。你会看到 backend-api 服务在疯狂地尝试重启,并不断打印出 Failed to connect... ECONNREFUSED 的错误。

1
2
3
4
5
6
7
my-app-db      | ... database system is ready to accept connections
backend-api | 🚀 API service starting, attempting to connect to DB...
backend-api | ❌ Failed to connect to the database: Error: connect ECONNREFUSED 172.21.0.2:5432
backend-api exited with code 1
backend-api | 🚀 API service starting, attempting to connect to DB...
backend-api | ❌ Failed to connect to the database: Error: connect ECONNREFUSED 172.21.0.2:5432
backend-api exited with code 1

痛点所在: Docker Compose 默认以 并行 方式启动所有服务。backend-api 启动得太快了,此时 db 容器虽然可能已经创建,但其内部的 PostgreSQL 服务进程还 没有完全初始化好并准备接受连接,导致 API 连接失败并崩溃。

请按下 Ctrl+C 并执行 docker compose down 清理环境。

第四步:使用 depends_onhealthcheck 确保服务可用

为了解决这个问题,我们需要一个组合拳:使用 depends_on 控制启动的 顺序,并使用 healthcheck 确保我们等待的是一个 真正可用 的服务。

请将最终的、完整的 docker-compose.yml 修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# docker-compose.yml (v0.4 - 健壮的依赖管理)

services:
db:
image: postgres:16-alpine
container_name: my-app-db
restart: always
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=appdb
volumes:
- db-data:/var/lib/postgresql/data
networks:
- my-app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d appdb"]
interval: 5s
timeout: 5s
retries: 5

backend-api:
container_name: backend-api
build: ./backend-api
depends_on:
db:
condition: service_healthy
networks:
- my-app-network
volumes:
- my-api-data:/app/data

frontend:
container_name: frontend
build: ./my-react-app
ports:
- "8080:80"
networks:
- my-app-network

networks:
my-app-network:
driver: bridge

volumes:
my-api-data:
driver: local
db-data:
driver: local

关键改动:

  • 我们在 db 服务下添加了 healthcheck 块,使用 PostgreSQL 自带的 pg_isready 工具来检查数据库是否就绪。
  • 我们将 backend-apidepends_on 修改为更精确的格式,明确要求它等待 db 服务的状态变为 healthy 后再启动。

最终验证

现在,再次执行 docker compose up -d 启动整个应用。然后立刻执行 docker compose ps 查看服务状态,你会看到 db 服务的状态最初是 running (health: starting),几秒后会变为 running (healthy)

此时,查看 backend-api 的日志 docker compose logs backend-api,你会看到一条干净利落的成功连接信息:

✅ Successfully connected to the database!


6.4. 解耦配置:环境变量与 .env 文件

承上启下: 在上一节,我们成功地为应用添加了数据库服务,并解决了启动依赖问题。但在 docker-compose.yml 文件中,我们留下了一个巨大的隐患:我们将数据库的用户名和密码 硬编码 在了配置文件里。将敏感凭证直接写入版本控制系统,是软件开发中的 头号禁忌

解决方案:使用 .env 文件进行配置解耦

Docker Compose 提供了一套优雅的机制来解决这个问题。它允许我们在 docker-compose.yml 中使用变量占位符,然后在一个名为 .env 的独立文件中定义这些变量的实际值。Compose 在启动时会自动读取 .env 文件,并将值注入到配置中。

核心优势:

  • 安全: 我们可以将 .env 文件添加到 .gitignore 中,确保敏感信息永远不会被提交到代码仓库。
  • 灵活: 团队中的每个成员都可以在本地维护自己的 .env 文件,而无需修改共享的 docker-compose.yml
  • 环境一致: 生产服务器上可以放置一个包含生产密码的 .env 文件,实现不同环境的配置切换。

第一步:创建 .env 文件

在您项目的根目录下(与 docker-compose.yml 文件位于同一级),创建一个名为 .env 的新文件。

1
2
3
4
5
6
# .env - 存储我们所有的敏感信息和环境特定配置

# Database Credentials
POSTGRES_USER=prorise_user
POSTGRES_PASSWORD=s3cr3t_p@ssw0rd_zxcv
POSTGRES_DB=prorise_db

在真实项目中,请立刻将 .env 文件添加到你的 .gitignore 文件中!

第二步:改造 docker-compose.yml 以使用变量

现在,我们修改 docker-compose.yml,用 ${VARIABLE_NAME} 的语法来引用 .env 文件中定义的变量。同时,我们也要将这些凭证传递给 backend-api 服务,以便它能连接到数据库。

请用以下内容 覆盖 你现有的 docker-compose.yml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# docker-compose.yml (v0.5 - 使用环境变量)

services:
db:
image: postgres:16-alpine
container_name: my-app-db
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- db-data:/var/lib/postgresql/data
networks:
- my-app-network
healthcheck:
#第二个 $ 和后面的变量 ${POSTGRES_USER} 应该被视为普通文本,直接传递给容器。
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5

backend-api:
container_name: backend-api
build: ./backend-api
depends_on:
db:
condition: service_healthy
networks:
- my-app-network
volumes:
- my-api-data:/app/data
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=${POSTGRES_USER}
- DB_PASSWORD=${POSTGRES_PASSWORD}
- DB_NAME=${POSTGRES_DB}

frontend:
container_name: frontend
build: ./my-react-app
ports:
- "8080:80"
networks:
- my-app-network
depends_on:
- backend-api

networks:
my-app-network:
driver: bridge

volumes:
my-api-data:
driver: local
db-data:
driver: local

第三步:改造 backend-api 以读取环境变量

我们的 backend-api 服务现在需要从环境变量(process.env)中读取数据库连接信息,而不是使用硬编码的值。

用以下内容 覆盖 backend-api/index.js 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// backend-api/index.js (读取环境变量)
const { Client } = require('pg');

const client = new Client({
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});

async function connectToDb() {
try {
await client.connect();
console.log('✅ Successfully connected to the database using credentials from .env file!');
await client.end();
} catch (err) {
console.error('❌ Failed to connect to the database:', err);
process.exit(1);
}
}

console.log('🚀 API service starting, attempting to connect to DB...');
connectToDb();

第四步:最终验证

所有改造都已完成。现在,我们来验证配置是否被成功加载。

首先,清理掉可能在运行的旧环境。

1
docker compose down

接着,使用 docker compose config 命令来预览 Compose 解析后的最终配置。 这是一个非常有用的调试工具。

1
docker compose config

在输出的 YAML 中,你应该能看到 dbbackend-api 服务的 environment 部分,${...} 占位符已经被 .env 文件中的 实际值 所替换。

然后,一键启动整个应用。

1
docker compose up -d

最后,查看 backend-api 的日志。

1
docker compose logs backend-api

你应该能看到那条成功的连接信息:
✅ Successfully connected to the database using credentials from .env file!


6.5. 环境隔离:使用 profiles 管理服务组合

承上启下: 我们的 docker-compose.yml 正在演变成一个完整的应用定义,包含了前端、后端和数据库。在真实的开发流程中,我们常常还需要一些 辅助服务,例如数据库管理工具、日志分析平台或测试工具。这些服务在开发和调试时非常有用,但在默认启动或生产部署时,我们并不希望运行它们。profiles 功能正是为了优雅地解决这个问题而设计的。

痛点背景:如何管理“可选”服务?

我们希望在 docker-compose.yml 中定义一个数据库管理工具(例如 Adminer),但我们不希望每次执行 docker compose up 时它都自动启动。我们只想在需要进行数据库调试时,才手动将它“激活”。

解决方案:为服务分配 profiles (配置文件)

profiles 关键字允许我们为一个或多个服务打上“标签”。被打上标签的服务,将不会被默认启动。只有当我们在命令行中明确激活该标签(profile)时,这些服务才会被创建和启动。

第一步:在 docker-compose.yml 中添加调试服务

我们来为应用栈添加一个 adminer 服务。Adminer 是一个轻量级的、通过 Web 界面管理多种数据库的工具。

请将 adminer 服务的定义,添加到您的 docker-compose.yml 文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# docker-compose.yml (v0.6 - 添加 Profile)

services:
db:
# ... db 服务保持不变 ...
image: postgres:16-alpine
container_name: my-app-db
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
volumes:
- db-data:/var/lib/postgresql/data
networks:
- my-app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 5s
timeout: 5s
retries: 5

backend-api:
# ... backend-api 服务保持不变 ...
container_name: backend-api
build: ./backend-api
depends_on:
db:
condition: service_healthy
networks:
- my-app-network
volumes:
- my-api-data:/app/data
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=${POSTGRES_USER}
- DB_PASSWORD=${POSTGRES_PASSWORD}
- DB_NAME=${POSTGRES_DB}

frontend:
# ... frontend 服务保持不变 ...
container_name: frontend
build: ./my-react-app
ports:
- "8080:80"
networks:
- my-app-network
depends_on:
- backend-api

adminer: # 新增内容:我们的数据库管理工具
image: adminer
restart: always
ports:
- "8081:8080" # 将主机的 8081 端口映射到 Adminer 的 8080 端口
networks:
- my-app-network
depends_on:
- db
profiles: # 关键配置:将此服务分配给名为 "debug" 的 profile
- "debug"

networks:
# ... 保持不变
my-app-network:
driver: bridge

volumes:
# ... 保持不变
my-api-data:
driver: local
db-data:
driver: local

第二步:验证默认启动行为

现在,我们来启动我们的应用栈。

1
docker compose up -d

启动完成后,我们来查看正在运行的服务。

1
docker compose ps

请注意,adminer 服务 没有 被启动。因为我们为它分配了一个 profile,它已经变成了一个“可选”服务。

第三步:激活 debug Profile

现在,我们假设需要调试数据库。我们可以使用 --profile 标志来同时启动默认服务和 debug profile 中的所有服务。

首先,清理掉当前的环境。

1
docker compose down

然后,使用 --profile 标志启动。

1
docker compose --profile debug up -d

再次查看正在运行的服务。

1
docker compose ps

这一次,adminer 服务成功启动了!

第四步:使用 Adminer 连接数据库

现在,打开你的 Windows 浏览器,访问 http://localhost:8081。你将看到 Adminer 的登录界面。

  • System: 选择 PostgreSQL
  • Server: 输入 db (这是我们在 Compose 中为数据库服务定义的名字!)
  • Username: 输入我们在 .env 文件中定义的 prorise_user
  • Password: 输入我们在 .env 文件中定义的 s3cr3t_p@ssw0rd_zxcv
  • Database: 输入我们在 .env 文件中定义的 prorise_db

点击登录,你就可以通过图形化界面来查看和管理 my-app-db 容器中的数据库了。

在结束本节前,请清理环境:

1
docker compose down

6.6. 本章核心速查总结:Docker Compose 常用配置与命令

承上启下: 恭喜您!您已经成功地从执行零散的 docker 命令,迈入了使用 docker-compose.yml 进行声明式服务编排的全新阶段。我们通过一步步的迭代,将一个多服务应用从手动管理的混乱状态,转化为了一个优雅、健壮、可一键启停的应用栈。本节将把本章所有核心的 YAML 关键字和 CLI 命令浓缩起来,作为您日后工作中可随时查阅的“弹药库”。1


docker-compose.yml 核心关键字速查

分类关键字核心描述与用法
服务定义services顶层关键字,所有独立的应用服务都在其下定义。
镜像来源build: <path>(推荐) 指定包含 Dockerfile 的路径,让 Compose 负责构建镜像。
镜像来源image: <name>:<tag>指定一个已经存在于本地或远程仓库的镜像。
容器配置container_name: <name>设置一个固定的、可读的容器名称,对应 docker run --name
网络ports: ["<host>:<container>"]映射端口,对应 docker run -p
网络networks: ["<net_name>"]将服务连接到在顶层 networks 块中定义的网络。
数据持久化volumes: ["<vol/path>:<ctn_path>"]挂载数据卷或绑定挂载,对应 docker run -v
配置environment: ["KEY=VALUE"]设置环境变量,支持从 .env 文件进行 ${VAR} 格式的变量替换。
启动控制depends_on控制服务启动顺序。推荐与 healthcheck 结合使用。
启动控制healthcheck定义一个命令来检查容器内应用是否真正健康、可用。
环境管理profiles: ["<profile_name>"]将服务分配给一个非默认的配置文件组,实现服务的按需启动。
资源声明networks / volumes顶层关键字,用于声明整个应用栈所需的网络和数据卷资源。

docker compose 核心命令速查

命令核心描述与常用参数
docker compose up(核心) 根据 docker-compose.yml 创建并启动所有服务。默认在前台运行。
-d: 在后台(detached)模式下运行。
--build: 强制重新构建所有服务的镜像,即使已存在。
--profile <name>: 激活指定 profile 中的服务。
docker compose down(核心) 停止并 移除 所有相关的容器、网络。
-v: 同时移除在 volumes 块中定义的具名数据卷。
docker compose ps列出当前 Compose 项目所管理的所有容器的状态。
docker compose logs [service_name]查看一个或所有服务的日志。
-f: 持续跟踪(follow)实时日志输出。
docker compose exec <service> <cmd>在指定服务的一个正在运行的容器中,执行一个命令。
docker compose build [service_name]构建或重新构建一个或所有服务的镜像。
docker compose config验证并显示经过变量替换和解析后的最终配置,是调试的利器。

高频面试题与陷阱

面试官深度追问
2025-09-20 10:56

docker-compose.yml 中,depends_onhealthcheck 是如何协同工作的?为什么说只用 depends_on 还不够可靠?

这是一个非常好的问题,它触及了多服务应用稳定启动的核心。

单独使用 depends_on,比如 depends_on: ['db'],它只保证了一件事:db 服务的 容器 会在 api 服务的 容器 启动之前被启动。但它完全不关心 db 容器 内部 的 PostgreSQL 进程是否已经完成了初始化、是否已经准备好接受外部连接。

这就会产生一个“竞态条件”:API 容器可能已经启动并开始尝试连接数据库,而此时数据库服务进程还在加载配置、检查文件,根本没“开门营业”,从而导致 API 连接失败并崩溃。

说得很好。那 healthcheck 是如何解决这个问题的?

healthcheck 解决了“知其然,并知其所以然”的问题。我们在数据库服务中定义一个 healthcheck,比如用 pg_isready 命令。Docker 会在容器启动后,定期执行这个命令。只有当 pg_isready 成功返回,Docker 才会将这个容器的状态标记为 healthy

然后,我们将 API 服务的 depends_on 升级为 depends_on: { db: { condition: service_healthy } }。这样一来,Compose 的行为就变成了:“启动 db 容器,然后持续等待,直到 db 服务的 healthcheck 状态变为 healthy然后,且仅当此时,才启动 api 服务”。这就完美地解决了竞态条件,确保了应用栈的启动是健壮和可预测的。