Docker Compose 入门教程:从零编排多容器应用与 WordPress 实战
结论先行:Docker Compose 把多容器应用栈(服务、内部网络、持久化数据卷、依赖顺序)固化为单个
compose.yaml文件,一条docker compose up -d即可在全新 VPS 上一键拉起整套生产架构。单机环境下,十个以内的关联服务编排完全无需引入 Kubernetes 复杂度。生产落地盯死四点:弃用已废弃的 V1(带连字符的docker-compose)、严禁在 YAML 明文硬编码密码(改用.env文件配合.gitignore)、通过健康检查(healthcheck)配合service_healthy确保数据库真正就绪、持久化目录统一使用声明式命名卷。
0、写在前面
在单容器时代,部署一套经典的 Web 应用流程繁琐且极易出错:
- 先敲命令手动创建一个 Docker 桥接网络;
- 运行
docker run启动 MySQL 容器,手动挂载数据卷、输入一长串环境变量; - 运行
docker run启动 Web 应用容器,将其加入同一网络并配置端口映射; - 运行
docker run启动 Nginx 作为反向代理,手动挂载配置文件; - 两个月后更换服务器或者新增开发成员时,若无精确笔记,这一长串参数很难完美复现。
任何一个端口号、环境变量或网络名称拼写错误,都会导致整个应用栈无法互通。
Docker Compose 是为了解决这种多容器协作问题而生的声明式工具。它通过结构化的 YAML 配置文件将所有服务、网络与存储关系全部代码化,实现「一份配置、处处秒级拉起」。
1、期望目标
阅读并完成本文实操后,你将达成以下目标:
- [ ] 掌握 Compose V2(Docker CLI 内置插件)的使用规范,彻底告别已淘汰的 V1 独立二进制;
- [ ] 深入理解
services、networks、volumes与environment核心语法; - [ ] 实操搭建一套生产级 Nginx + WordPress + MySQL 8.0 容器编排架构;
- [ ] 掌握
.env环境变量文件管理机制,杜绝数据库密码与密钥明文提交至 Git 仓库; - [ ] 掌握健康检查(Healthcheck)机制,彻底解决容器启动早于数据库导致连接报错的经典故障;
- [ ] 熟练运用堆栈更新、热重启、指定服务重构与日志排障命令。
2、技术架构与网络拓扑
Docker Compose V1 vs V2 差异
很多旧教程依然在使用带有中划线的 docker-compose 命令,这一版本已在 2023 年 6 月正式到达生命周期终点(EOL)。
| 评估维度 | Docker Compose V1(已淘汰) | Docker Compose V2(当前标准) |
|---|---|---|
| 调用命令 | docker-compose(带连字符) |
docker compose(中间为空格) |
| 底层实现 | 基于 Python 编写的独立二进制文件 | 基于 Go 语言重写,原生内置于 Docker CLI |
| 版本规范 | 顶部强制声明 version: '3.8' 等版本号 |
遵循统一 Compose Spec,无需声明顶层 version |
| 执行性能 | 命令解析启动慢,多容器并发拉取效率低 | 启动执行速度提升约 30%,错误栈定位更精准 |
| 高级特性 | 不支持现代语法特性 | 原生支持 watch 热更新、环境检查与配置校验 |
Nginx + WordPress + MySQL 网络拓扑
在默认情况下,Docker Compose 会自动为项目创建独立的自定义网桥。容器之间无需对外暴露数据库端口,只需使用服务名即可在内网中通过内置 DNS 实现安全通信。
3、实操步骤
3.1 环境验证与 Compose V2 安装
现代 Linux 系统上安装 Docker 时已默认自带 Compose 插件。首先在终端运行验证:
1 | docker compose version |
若能正确输出形如 Docker Compose version v2.29.x 的版本信息,即可直接使用。
若提示 compose is not a docker command,表明当前 Docker 引擎版本过老或缺少插件。推荐使用 ITTinker 开源的生产级部署脚本直接修复安装:
1 | # 一键安装最新的官方 Docker CE 与 Docker Compose V2 插件 |
3.2 Compose YAML 核心语法结构
官方 Compose 文件标准命名为 compose.yaml(兼容历史习惯的 docker-compose.yml)。一个标准文件的顶层通常包含三个核心段落:
1 | # 1. 服务定义(每一个子项代表一个独立的容器) |
3.3 实战:编排生产级 Nginx + WordPress + MySQL 8.0 架构
下面通过实战编排一套开箱即用的海外 WordPress 站点。项目包含三个服务:
db:MySQL 8.0 数据库,数据卷持久化,配置健康检查;wordpress:核心应用容器,依赖db的健康状态;nginx:前端反向代理,挂载宿主反代配置,只开放 80/443 给外部访问。
步骤 1:规划项目目录
1 | mkdir -p ~/deploy-wordpress/{nginx/conf.d,html} |
步骤 2:创建环境变量文件 .env
敏感密码严禁写在 YAML 文件中。我们在同级目录创建 .env:
1 | cat > .env <<'EOF' |
将 .env 加入本地版本控制黑名单:
1 | echo ".env" >> .gitignore |
步骤 3:编写 nginx.conf 反代配置
创建 nginx/conf.d/default.conf:
1 | server { |
步骤 4:编写生产级 compose.yaml
1 | cat > compose.yaml <<'EOF' |
3.4 堆栈生命周期管理指令
进入项目所在目录(即 compose.yaml 所在的目录),执行以下管理指令:
1 | # 1. 启动并后台运行全部服务(若镜像不存在会自动并行拉取) |
4、生产避坑指南与排障速查
避坑 1:depends_on 未配置健康探针引发「数据库连接拒绝」
- 现象:执行
docker compose up -d后,wordpress容器报错退出,日志显示Error establishing a database connection或Connection refused。 - 根因:基础语法中的
depends_on: [db]仅仅意味着「Docker 在启动 wordpress 前先给 db 发送创建指令」。然而 MySQL 容器启动后,内部需要花费 10 至 30 秒执行首次数据目录初始化与权限刷新。此时应用去连接数据库,端口未就绪,引发崩溃。 - 解法:必须如本文示例,在
db服务中编写healthcheck,并在应用层声明condition: service_healthy。
避坑 2:容器内部通信误用 localhost
- 现象:Node.js 或 WordPress 报错
connect ECONNREFUSED 127.0.0.1:3306。 - 根因:在默认的 Bridge 网络下,每个容器都拥有独立的 Network Namespace。容器内的
localhost或127.0.0.1指向容器本身,而非宿主机或其他容器。 - 解法:在网络互联时,连接目标必须填写服务名称(例如
db、redis),Docker 内部 DNS 会自动将其解析为对应容器的内网 IP。
避坑 3:.env 文件等号周围带有非法空格
- 现象:环境变量解析出的值带有前缀空格,例如实际读到
" db"而不是"db",导致认证持续失败。 - 根因:Docker Compose 对
.env的解析规则要求键值对中间不得插入无意义的空格。 - 规范写法:
1
2
3
4
5
6
7# 错误格式(禁止加空格)
DB_HOST = db
DB_PASS = 123456
# 正确格式
DB_HOST=db
DB_PASS=123456
5、FAQ(高频问题解答)
Q1:新建的 compose.yaml 文件还需要写顶层 version: '3.8' 吗?
不需要。自 Docker Compose V2 统一采用 Compose Specification 规范以来,顶层 version 字段已被废弃。保留该字段会触发运行时警告,新项目直接从 services: 段落开始书写即可。
Q2:修改了代码或者某一个服务的配置,必须全部重启吗?
不需要。运行以下命令即可针对单一服务进行差异化更新:
1 | # 只重新构建并热重载名为 app 的单一容器,其他正在运行的 db 与 redis 不受任何影响 |
Q3:一台 VPS 上管理多个独立项目,Compose 的网络会冲突吗?
不会。Docker Compose 默认使用当前项目所在目录名作为堆栈前缀(例如目录名为 proj-a,创建的网络会自动命名为 proj-a_default)。不同目录下的项目默认彼此网络隔离,只要宿主机对外映射的端口不发生冲突,多个堆栈即可和谐共存。