Docker Compose
只用 docker run 启动多个容器时,端口、网络、卷和环境变量会散落在命令历史中。Docker Compose 使用一个 YAML 文件声明整个应用,让团队可以用一致的配置启动和停止服务。
本篇使用 Nginx 和 Redis 构建一个可直接运行的双服务项目。完成后,你将能够:
- 看懂
compose.yaml的核心结构; - 使用 Compose V2 启动和管理多容器应用;
- 理解服务名、项目名、默认网络和命名卷;
- 区分
up、run、exec、stop和down; - 安全地更新配置和清理环境。
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
预期看到 web 和 cache 两项均为运行状态,健康检查完成后显示 healthy。浏览器打开 http://localhost:8080,或执行:
# 从宿主机验证 Web 服务
curl http://127.0.0.1:8080
docker compose up 自动完成了几件事:
- 创建一个以项目名开头的默认网络;
- 创建
redis-data对应的项目卷; - 创建并启动两个服务容器;
- 把服务名
web、cache注册到项目网络 DNS; - 只把
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
日常停止或更新使用普通的 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. 本篇练习
- 为
web添加自定义环境变量,用docker compose config查看最终结果; - 删除并重建
cache容器,确认命名卷中的键仍存在; - 用
docker compose run --rm启动临时 Alpine 服务访问http://web; - 分别执行
stop、start、down,观察容器 ID 和数据是否变化。
下一篇将从镜像、运行参数、网络、密钥和宿主机五个层面建立 Docker 安全基线。