跳到主要内容

Docker Compose

只用 docker run 启动多个容器时,端口、网络、卷和环境变量会散落在命令历史中。Docker Compose 使用一个 YAML 文件声明整个应用,让团队可以用一致的配置启动和停止服务。

本篇使用 Nginx 和 Redis 构建一个可直接运行的双服务项目。完成后,你将能够:

  • 看懂 compose.yaml 的核心结构;
  • 使用 Compose V2 启动和管理多容器应用;
  • 理解服务名、项目名、默认网络和命名卷;
  • 区分 uprunexecstopdown
  • 安全地更新配置和清理环境。

1. 确认使用 Compose V2

# Compose V2 是 docker 的子命令,中间为空格
docker compose version

# 同时确认 Docker Engine 正常运行
docker version

本系列使用 docker compose。旧的 docker-compose 独立程序已经停止作为推荐方案,新项目不要再依赖它。

2. 创建第一个 Compose 项目

2.1 准备目录和首页

# Compose 默认使用目录名作为项目名,因此目录应简短明确
mkdir -p ~/docker-beginner/compose-demo/site
cd ~/docker-beginner/compose-demo

site/index.html 中写入:

<!-- 该页面由 compose.yaml 以只读方式挂载到 Nginx -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<title>Compose 入门</title>
</head>
<body>
<h1>Docker Compose 运行成功</h1>
</body>
</html>

2.2 编写 compose.yaml

在项目根目录创建 compose.yaml

# 顶层 services 定义项目包含哪些长期运行的服务
services:
web:
# 固定镜像版本,避免不同时间拉取到意外版本
image: nginx:1.27-alpine
# 只让宿主机本地访问 8080,由 Docker 转发到容器 80
ports:
- "127.0.0.1:8080:80"
# 把本地网页目录只读挂载到 Nginx 默认站点目录
volumes:
- ./site:/usr/share/nginx/html:ro
# 容器异常退出或 Engine 重启时自动恢复,手动停止除外
restart: unless-stopped
# wget 成功返回才认为 Web 服务健康
healthcheck:
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1/"]
interval: 10s
timeout: 3s
retries: 3
start_period: 5s

cache:
# Redis 只加入项目网络,不发布宿主机端口
image: redis:7.4-alpine
# 开启 AOF,使数据写入下面的命名卷
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5

# 顶层 volumes 声明由 Compose 管理的命名卷
volumes:
redis-data:

Compose 规范现在不要求顶层 version 字段。旧教程里的 version: "3" 可以删除,Compose V2 会根据当前规范解析文件。

3. 先校验,再启动

# 解析 YAML、变量和默认值,并输出最终配置;不创建容器
docker compose config

# 后台创建并启动服务;--wait 等待服务运行或健康
docker compose up --detach --wait

# 查看项目服务、容器状态、健康状态和端口
docker compose ps

预期看到 webcache 两项均为运行状态,健康检查完成后显示 healthy。浏览器打开 http://localhost:8080,或执行:

# 从宿主机验证 Web 服务
curl http://127.0.0.1:8080

docker compose up 自动完成了几件事:

  1. 创建一个以项目名开头的默认网络;
  2. 创建 redis-data 对应的项目卷;
  3. 创建并启动两个服务容器;
  4. 把服务名 webcache 注册到项目网络 DNS;
  5. 只把 web 的 8080 端口发布到宿主机。

4. 服务名就是网络地址

Compose 默认把同一项目的服务接入同一网络。其他服务应使用 cache:6379 访问 Redis,而不是 localhost:6379

# 临时运行 redis-cli,并通过服务名 cache 访问 Redis
docker compose run --rm \
cache \
redis-cli -h cache ping

# 写入一个测试值
docker compose exec cache \
redis-cli set tutorial compose

# 读取测试值;预期输出 compose
docker compose exec cache \
redis-cli get tutorial

这里有两个容易混淆的命令:

  • docker compose exec cache ...:在已经运行的 cache 容器中执行命令;
  • docker compose run --rm cache ...:根据 cache 配置创建一个一次性新容器执行命令。

5. 查看日志和状态

# 查看所有服务最近 50 行日志
docker compose logs --tail 50

# 持续跟踪 web 日志;按 Ctrl+C 只停止查看,不停止服务
docker compose logs --follow --tail 20 web

# 查看项目内进程
docker compose top

# 查看项目容器的 CPU、内存和网络使用快照
docker compose stats --no-stream

# 输出项目创建的网络和卷名称
docker compose config --volumes
docker compose config --networks

Compose 命令应在 compose.yaml 所在目录运行。也可以在其他目录使用 -f /绝对路径/compose.yaml 明确指定文件。

6. 修改配置并更新容器

web 的端口从 8080 改为 8081,然后执行:

# 先再次校验修改后的最终配置
docker compose config --quiet

# 只重建配置有变化的容器,未变化服务保持运行
docker compose up --detach --wait

# 确认 web 使用新的宿主机端口
docker compose ps web

修改 site/index.html 不需要重建容器,因为它是 bind mount。修改镜像标签、端口、环境变量或挂载配置时,up 会按需要替换容器。

如果服务由本地 Dockerfile 构建,可以这样声明:

services:
api:
# 构建上下文是 compose.yaml 所在目录下的 api 目录
build:
context: ./api
dockerfile: Dockerfile
# 同时给构建结果设置一个易读标签
image: beginner-api:1.0
# 重新构建本地镜像,再创建或替换服务容器
docker compose up --detach --build

7. 环境变量与 .env

Compose 支持变量替换。创建一个不提交到 Git 的 .env

# Compose 读取该值并替换 compose.yaml 中的 ${WEB_PORT}
WEB_PORT=8080

配置中引用:

services:
web:
image: nginx:1.27-alpine
ports:
# 未提供 WEB_PORT 时使用默认值 8080
- "127.0.0.1:${WEB_PORT:-8080}:80"
# 查看变量替换后的端口,确认没有意外空值
docker compose config

.env 主要用于 Compose 插值,不应当作安全的密钥库。密码可能通过 docker inspect、进程环境或日志暴露。生产密钥应使用部署平台的 secret 机制,并确保 .env 已加入 .gitignore

8. 启停与删除的区别

# 停止服务容器,但不删除容器、网络或卷
docker compose stop

# 重新启动之前停止的同一批容器
docker compose start

# 停止并删除服务容器和项目默认网络;命名卷默认保留
docker compose down

# 再次启动会创建新容器,并继续使用保留的 Redis 卷
docker compose up --detach --wait

# 验证之前写入卷的 tutorial 键仍然存在
docker compose exec cache \
redis-cli get tutorial
命令容器默认网络命名卷
stop保留但停止保留保留
start启动已有容器保留保留
down删除删除保留
down --volumes删除删除删除

9. 最终清理实验

先确认不再需要 Redis 中的实验数据:

# 显示当前项目状态和卷声明,执行删除前最后确认
docker compose ps
docker compose config --volumes

# 删除容器、网络和本项目命名卷;卷内实验数据永久删除
docker compose down --volumes --remove-orphans

# 确认项目容器已经清理
docker compose ps --all
不要随意添加 --volumes

日常停止或更新使用普通的 docker compose down 即可。只有确认数据不再需要或已有可恢复备份时,才执行 down --volumes

10. 常见错误

现象原因和处理
no configuration file provided当前目录没有 Compose 文件;切换目录或使用 -f
port is already allocated宿主机端口冲突;改端口或停止占用者
服务访问 localhost 失败localhost 指当前容器;改用 Compose 服务名
变量为空或插值异常docker compose config 检查最终值
修改 Dockerfile 后未生效执行 docker compose up -d --build
depends_on 后依赖仍断开启动顺序不等于持续可用;应用仍要实现重试和超时

11. 本篇练习

  1. web 添加自定义环境变量,用 docker compose config 查看最终结果;
  2. 删除并重建 cache 容器,确认命名卷中的键仍存在;
  3. docker compose run --rm 启动临时 Alpine 服务访问 http://web
  4. 分别执行 stopstartdown,观察容器 ID 和数据是否变化。

下一篇将从镜像、运行参数、网络、密钥和宿主机五个层面建立 Docker 安全基线。