外观
项目 8:Docker Compose 编排 WordPress 多容器应用
约 2799 字大约 9 分钟
DockerDocker ComposeWordPressMySQL
2026-06-07
项目目标
前七节课中,你每次只能启动一个容器——Nginx、MySQL、Redis——然后手工用 --link 或者自定义网络把它们连起来。每部署一次就要敲一大串 docker run 命令,参数一多就容易出错。
本课引入 Docker Compose——把多个容器的定义写进一个 compose.yml 文件,一条命令启动整个应用栈。你将用 Compose 编排 WordPress(PHP 博客系统)+ MySQL 数据库,掌握 services、networks、volumes、environment、depends_on 等核心概念,并通过扩展 phpMyAdmin 服务体验"加一个服务只需加几行 YAML"的开发效率。
一、为什么需要 Docker Compose?
想一想你在第 5 课和第 6 课做过的事:
# 1. 创建网络
docker network create my-net
# 2. 创建数据卷
docker volume create mysql-data
# 3. 启动数据库
docker run -d --name db --network my-net \
-v mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret123 \
mysql:8.0
# 4. 启动 WordPress
docker run -d --name wp --network my-net \
-p 80:80 \
-e WORDPRESS_DB_HOST=db \
-e WORDPRESS_DB_USER=root \
-e WORDPRESS_DB_PASSWORD=secret123 \
wordpress:latest每次部署都要重复敲这四步。而且当你需要停掉整个应用、重建、迁移到另一台机器时,你需要在脑子里记住这些容器的依赖关系和启动顺序。
Docker Compose 把这些命令变成了一个文件:
services:
db:
image: mysql:8.0
volumes:
- mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret123
wp:
image: wordpress:latest
ports:
- "80:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_PASSWORD: secret123
depends_on:
- db
volumes:
mysql-data:启动整个应用:docker compose up -d
停掉整个应用:docker compose down
YAML 文件即文档,版本管理即审计——这就是 Compose 的核心价值。
二、compose.yml 核心要素
Compose 文件的顶层元素有四个关键块:
| 元素 | 作用 | 类比 |
|---|---|---|
services | 定义每个容器的镜像、端口、环境变量等 | 一个 docker run 命令 |
networks | 显式定义容器间的通信网络 | docker network create |
volumes | 显式声明需要持久化的数据卷 | docker volume create |
configs / secrets | 配置文件与敏感信息管理(进阶) | 配置文件注入 |
services 中的常用配置项
services:
my-service:
image: nginx:alpine # 使用已有镜像
# build: ./app # 或从 Dockerfile 构建
container_name: my-nginx # 容器名称
ports:
- "8080:80" # 宿主机端口:容器端口
environment:
MY_VAR: value # 注入环境变量
volumes:
- my-vol:/data # 挂载命名卷
- ./config:/etc/nginx/conf.d # 挂载本地目录(Bind Mount)
networks:
- my-net # 加入自定义网络
depends_on:
- db # 声明依赖(只控制启动顺序,不等待就绪)
restart: always # 重启策略depends_on 的陷阱
depends_on 只保证启动顺序(先启动 db,再启动 wp),但不等 db 真正就绪。数据库容器启动后还需要几秒才能接受连接。
如果在 WordPress 的启动脚本中加上 wait-for-it.sh 等就绪检查,才能真正解决"数据库还没好,WordPress 就连不上"的问题。这个技巧会在第 9 课中深入讲解。
Docker Compose 启动顺序与 depends_on 陷阱
对比普通 depends_on 与结合 healthcheck 后的容器启动行为
服务: db (MySQL)未启动
📦
等待启动...
depends_on
服务: wordpress未启动
📦
等待启动...
三、实战:WordPress + MySQL 双服务编排
3.1 创建项目目录
mkdir -p ~/wordpress-compose
cd ~/wordpress-compose3.2 编写 compose.yml
创建 compose.yml:
问题:在 Compose 文件中,声明一个服务依赖另一个服务启动顺序的关键词是什么?(全小写,单词间用下划线连接,10个字母)
services:
db:
image: mysql:8.0
container_name: wp-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: wp-secret-2024
MYSQL_DATABASE: wordpress
MYSQL_USER: wp-user
MYSQL_PASSWORD: wp-pass-2024
volumes:
- wp-db-data:/var/lib/mysql
networks:
- wp-net
wordpress:
image: wordpress:latest
container_name: wp-app
restart: always
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wp-user
WORDPRESS_DB_PASSWORD: wp-pass-2024
WORDPRESS_DB_NAME: wordpress
depends_on:
- db
networks:
- wp-net
volumes:
wp-db-data:
networks:
wp-net:
driver: bridge3.3 关键设计决策解读
为什么数据库使用独立用户而非 root?
# 不推荐:直接用 root
MYSQL_ROOT_PASSWORD: secret
WORDPRESS_DB_USER: root
# 推荐:创建专用数据库用户
MYSQL_USER: wp-user
MYSQL_PASSWORD: wp-pass-2024WordPress 只需要访问它自己的数据库,不需要 MySQL 的全部管理权限。用专用用户遵循最小权限原则。
为什么 Compose 不指定 container_name 也能通过服务名通信?
Compose 会自动为服务创建 DNS 记录。在 wp-net 网络中:
wordpress容器可以直接ping db——它会解析为db容器的 IP- 这是第 5 课自定义 bridge 网络 DNS 的延伸——Compose 帮你自动化了
Docker Compose 服务发现机制
演示 WordPress 容器如何通过服务名 "db" 找到 MySQL 容器的内部 IP
WordPress 容器
IP: 172.20.0.3
WORDPRESS_DB_HOST: db🌐
Docker 内置 DNS
MySQL 容器
IP: 172.20.0.2
Service: db
3.4 启动应用栈
# 启动所有服务(-d 后台运行)
docker compose up -d
# 查看运行状态
docker compose ps输出示例:
NAME IMAGE COMMAND SERVICE STATUS
wp-app wordpress:latest "docker-entrypoint.s…" wordpress running
wp-db mysql:8.0 "docker-entrypoint.s…" db running3.5 观察启动日志
# 查看所有服务的日志(混合输出)
docker compose logs
# 只看 WordPress 的日志
docker compose logs wordpress
# 跟踪最新日志
docker compose logs -f初始日志中你会看到 MySQL 的初始化过程(创建数据库、创建用户、设置密码),然后是 WordPress 的 Apache 启动日志。
3.6 访问 WordPress 并完成初始化
- 浏览器打开
http://127.0.0.1:8080(远程实验机使用http://宿主机IP:8080) - 选择语言 → 设置站点标题、管理员用户名、密码、邮箱
- 登录后台,创建一篇测试文章
- 在首页确认文章正常显示
四、数据持久化验证
WordPress 的数据分为两部分:
- 数据库(文章、用户、设置)→ 存储在 MySQL 中,持久化到
wp-db-data数据卷 - 上传文件(图片、附件)→ 存储在 WordPress 容器的
/var/www/html/wp-content/uploads
本课的 compose.yml 只持久化了数据库。下面的实验帮你理解持久化的效果:
# 1. 确认 WordPress 站点正常运行后,停掉所有容器
docker compose down
# 2. 观察:数据卷还在
docker volume ls | grep wp-db-data
# 3. 重新启动
docker compose up -d
# 4. 访问 WordPress——之前创建的文章和设置都在
# 因为 wp-db-data 数据卷保留了 MySQL 的所有数据# 警告:加上 -v 会删除数据卷!
docker compose down -v
# 如果有重要数据,这步不可逆!docker compose down 的三种破坏级别
| 命令 | 删除容器 | 删除网络 | 删除数据卷 |
|---|---|---|---|
docker compose down | ✅ | ✅ | ❌ |
docker compose down --volumes / -v | ✅ | ✅ | ✅ |
docker compose down --rmi all | ✅ | ✅ | ❌(但删镜像) |
五、扩展:增加 phpMyAdmin 服务
phpMyAdmin 是一个 Web 界面的 MySQL 管理工具。增它只需在 compose.yml 的 services 下加几行:
services:
# ... db 和 wordpress 保持不变 ...
phpmyadmin:
image: phpmyadmin:latest
container_name: wp-pma
restart: always
ports:
- "8081:80"
environment:
PMA_HOST: db # 指向 MySQL 服务名
PMA_USER: root
PMA_PASSWORD: wp-secret-2024
depends_on:
- db
networks:
- wp-net加完后执行:
docker compose up -d
# Compose 只会启动新服务,已有服务不受影响
docker compose ps
# 现在应该看到三个服务:db、wordpress、phpmyadmin访问 http://127.0.0.1:8081,用 root / wp-secret-2024 登录,就能在 Web 界面中浏览和管理 wordpress 数据库。
Compose 的增量更新
当你修改 compose.yml 后执行 docker compose up -d,Compose 会对比当前状态和目标状态:
- 已存在且未变化的服务 → 跳过
- 新加的服务 → 创建并启动
- 配置变化的服务 → 重新创建
六、Compose 生命周期命令速查
docker compose up -d # 创建并启动所有服务
docker compose up -d --build # 重新构建镜像后启动
docker compose ps # 查看服务状态
docker compose logs -f [svc] # 查看日志
docker compose exec db bash # 进入数据库容器的 shell
docker compose stop # 停止所有服务(不删除容器)
docker compose start # 重新启动已停止的服务
docker compose restart # 重启所有服务
docker compose down # 停止并删除容器、网络
docker compose down -v # 同时删除数据卷(小心!)
docker compose config # 验证 compose.yml 语法七、常见问题排查
| 现象 | 排查方法 |
|---|---|
Error: YAML syntax error | 检查缩进——YAML 用空格缩进,通常是 2 空格;不能混用 Tab |
| WordPress 显示"建立数据库连接错误" | docker compose logs db 查看数据库是否完成初始化;等 10-15 秒后刷新 |
| MySQL 容器反复重启 | docker compose logs db 查看错误日志——通常是环境变量或数据卷权限问题 |
| 端口 8080 已被占用 | 修改 compose.yml 中的 ports 为 "8088:80" 或其他未被占用的端口 |
docker compose 命令不可用 | Docker Desktop 自带 Compose v2;Linux 上需要单独安装 docker-compose-plugin |
八、提交物清单
| 文件 | 说明 |
|---|---|
compose.yml | 完整的 WordPress + MySQL + phpMyAdmin 编排文件 |
docker compose ps 截图 | 显示三个服务运行状态 |
docker compose logs 截图 | 显示数据库初始化和 WordPress 启动日志 |
| WordPress 管理后台截图 | 显示已登录的状态 |
| 测试文章前端截图 | 证明文章可以成功发布和展示 |
| 数据持久化验证截图 | docker compose down 后重新 up -d,数据仍在 |
troubleshooting.md | 至少三条你在操作中遇到并解决的问题 |
当堂检测
compose.yml中的depends_on能保证数据库在 WordPress 连接前就绪吗?如果不能,有什么解决方案?docker compose down和docker compose down -v的区别是什么?什么场景下不能加-v?- phpMyAdmin 通过什么环境变量连接数据库?它需要加入哪个网络?
- Compose 如何实现容器间的服务发现?为什么可以用服务名直接通信?
九、深度探索任务
以下任务不强制提交,是拔高性质的进阶练习。
探索任务 1:理解 Compose 的资源命名规则
目标:理解 Compose 自动创建的容器、网络、数据卷的命名规则。
cd ~/wordpress-compose
# 查看 Compose 创建的网络
docker network ls | grep wp-net
# 输出类似:wordpress-compose_wp-net
# 查看 Compose 创建的数据卷
docker volume ls | grep wp-db-data
# 输出类似:wordpress-compose_wp-db-data
# Compose 的命名规则:<项目目录名>_<资源名>
# 项目目录名为 wordpress-compose,所以:
# - 网络:wordpress-compose_wp-net
# - 数据卷:wordpress-compose_wp-db-data确认问题
如果在另一个目录 blog-project 中也写了一份完全相同的 compose.yml 并启动,会和 wordpress-compose 项目冲突吗?为什么?
探索任务 2:为 WordPress 上传文件增加持久化
目标:解决第 4.3 节中提到的问题——上传的图片和附件目前没有持久化。
在 compose.yml 的 wordpress 服务中增加一个 Volume 挂载:
services:
wordpress:
# ... 现有配置 ...
volumes:
- wp-uploads:/var/www/html/wp-content/uploads
volumes:
wp-db-data:
wp-uploads: # 新增重新启动后:上传一张图片到头像或文章中 → docker compose down → docker compose up -d → 图片还在。
确认问题
WordPress 的哪些数据应该持久化?本课的 compose.yml 默认只持久化了数据库——上传的图片、安装的插件和主题在容器删除后会怎样?除了 /var/www/html/wp-content/uploads,还有哪些目录需要持久化?
探索任务 3:使用环境变量文件 .env
目标:把密码从 compose.yml 中抽离到 .env 文件,避免密码明文提交到 Git。
创建 .env 文件:
MYSQL_ROOT_PASSWORD=wp-secret-2024
MYSQL_DATABASE=wordpress
MYSQL_USER=wp-user
MYSQL_PASSWORD=wp-pass-2024
WORDPRESS_DB_HOST=db修改 compose.yml,用 ${VAR} 引用:
services:
db:
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}创建 .env.example 作为模板(不含真实密码):
MYSQL_ROOT_PASSWORD=change-me
MYSQL_DATABASE=wordpress
MYSQL_USER=wp-user
MYSQL_PASSWORD=change-me把 .env 加入 .gitignore,只提交 .env.example。
确认问题
为什么不应该把 .env 提交到 Git?docker compose config 命令可以帮你验证什么?
