← 全部笔记

Docker · 6 Compose 多容器

实战:Web + 数据库两容器应用

Web 接收请求,数据库保存文章。把数据放进命名卷,删除并重建容器后,仍能读回同一篇文章。

约 15 分钟 · 截至 2026-11-03,Docker Engine 29.x · 实验未实测 · 命令面向 Compose v2

你的浏览器→ 访问127.0.0.1:8080→ 转发给 web:80

web-db-lab_default · Compose 网络

web · WordPress显示页面、处理文章
读写文章db:3306
db · MariaDB保存文章、用户、设置

web ↓ 挂载 /var/www/html

wp_data · 程序与上传文件

db ↓ 挂载 /var/lib/mysql

db_data · 数据库文件
图 1 请求在两个容器间流动;两个命名卷独立于容器保留。[1] [3]
未实测

编写环境没有 Docker。配置已通过独立 Compose 2.40.3 离线校验;下面的应用运行和预期验收条件均未实测,没有把预期结果写成终端记录。维护者需补做应用与持久化实测。

先确定连接路径

Web 按服务名找数据库

本页选用 WordPress(文章发布程序)和 MariaDB(兼容 MySQL 协议的数据库)。镜像分别自带 Web 运行环境和数据库服务,你不需要编写业务代码。

web 通过服务名 db 连接数据库。Compose 默认把它们接入同一个网络,服务名由 Docker 的域名解析系统(DNS)解析为容器地址。浏览器则通过宿主机的端口映射进入 Web。[1]

看图预测:在 web 容器里,把数据库地址改成 localhost:3306,会连到哪里?

localhost 是当前容器自己的回环地址。这里应写 db:3306。db 无需发布宿主机端口,同网络的 web 直接访问它的容器端口。[1]

准备同一台机器上的终端和浏览器,确认 Docker Engine 与 Compose v2 可用。基础对象可回看镜像、容器、仓库,运行链路可回看第一次 docker run。

docker version
docker compose version
实验影响

后面的 up 会拉取镜像、创建两个容器、两个命名卷和一个网络,并映射 127.0.0.1:8080。Docker 会配置相应网络与端口转发规则;不需要手动修改 iptables 或使用 --privileged。请在本机练习环境操作。[9]

步骤 1 · 准备文件

两端使用同一组凭据

在一个新建的专用目录里保存下面两个文件。所有后续 Compose 命令都在此目录执行。不要与已有的 web-db-lab 项目混用。

mkdir web-db-lab
cd web-db-lab

先保存 .env

这是提供给 Compose 的变量文件。把示例密码换成你自己的两组不同值;本练习用字母和数字即可。变量名使用 LAB_ 前缀,若终端已导出同名变量,先取消导出:Shell 中的值会优先于 .env。[2]

LAB_DB_PASSWORD=ChangeMeApp29
LAB_ROOT_PASSWORD=ChangeMeRoot29

.env 不会自动把所有变量塞进容器。下一份文件里的 environment 才指定传给各容器的变量;${变量名:?提示} 表示值为空或缺失时直接报错。[2]

再保存 compose.yaml

这两项是已核对存在的具体镜像标签,供本实验使用。name 固定项目名,让后续命令找到同一组卷;不要在中途切换 -p 项目名。

name: web-db-lab

services:
  web:
    image: wordpress:6.8.3-php8.3-apache
    ports:
      - "127.0.0.1:8080:80"
    environment:
      WORDPRESS_DB_HOST: db:3306
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wp
      WORDPRESS_DB_PASSWORD: ${LAB_DB_PASSWORD:?请先设置 LAB_DB_PASSWORD}
    volumes:
      - wp_data:/var/www/html
    depends_on:
      db:
        condition: service_healthy

  db:
    image: mariadb:11.4.5
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wp
      MARIADB_PASSWORD: ${LAB_DB_PASSWORD:?请先设置 LAB_DB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${LAB_ROOT_PASSWORD:?请先设置 LAB_ROOT_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      start_period: 30s
      interval: 5s
      timeout: 3s
      retries: 20

volumes:
  wp_data:
  db_data:

MariaDB 在空数据目录上创建 wordpress 数据库和 wp 用户。WordPress 用同名数据库、同一用户和密码连接;它不负责创建数据库。Web 使用普通应用账号,root 密码仅供数据库管理。[3] [4]

.env 提供值LAB_DB_PASSWORD
Compose 替换
容器分别收到web: WORDPRESS_DB_PASSWORDdb: MARIADB_PASSWORD
图 2 两个不同的镜像变量名,引用同一个 Compose 变量值。

不看配置补全:web 连接 db 时使用什么地址?

WORDPRESS_DB_HOST:

db:3306 包含服务名和数据库容器端口。宿主机的 8080 只映射到 Web 的 80。

深入一点:.env 与密码管理

.env 是明文文件,应留在本机并排除出版本控制。容器环境变量也不是加密保险箱。真正部署时可用镜像支持的 _FILE 变量配合 Compose secrets(把敏感值以文件交给服务),本页只练习本地配置链路。[3] [4]

标签固定了本实验的镜像选择,但标签仍可被重推;严格复现可进一步固定镜像摘要。WordPress 的程序文件保存在卷内,后台更新也可能改变它们;本实验期间不要升级程序或安装插件。

步骤 2 · 启动并等待

数据库就绪后再启动 Web

健康检查是在容器里定期执行的探测命令。这里检查数据库能接受 TCP 连接、且 InnoDB(数据库存储引擎)已完成初始化。它不验证 WordPress 的账号密码。[5]

健康检查放行 Web 的顺序 Compose 启动 db,db 初始化并通过健康检查后,Compose 才启动 web,web 随后连接 db。时间向下,只表示先后。 Compose db web 启动 db 初始化 探测通过 healthy 启动 web 连接 db
  1. Compose 按依赖先启动 db。
  2. db 正在初始化,运行中尚不代表可用。预测:现在应放行 web,还是继续等探测通过?
  3. 探测命令成功:端口可连接,InnoDB 初始化完成。
  4. 容器健康状态变为 healthy,Compose 等待条件得到满足。
  5. service_healthy 放行 web 的启动。
  6. web 用 db:3306 和应用账号连接数据库;账号是否正确仍需应用验证。
图 3 向下表示先后;健康条件把“db 运行中”和“允许 web 启动”分开。[6]

start_period 给初始化留出宽限,期间失败不累计到失败阈值;之后连续失败达到 retries 会判为不健康。interval 是探测间隔,timeout 是单次探测超时;这些数值是本例配置,不是初始化耗时承诺。

先检查,再启动

docker compose config --quiet
docker run --rm --entrypoint php wordpress:6.8.3-php8.3-apache \
  -r 'echo "PHP ready\n";'
docker compose up -d --wait --wait-timeout 180
docker compose ps

预期验收(未实测):config --quiet 成功时不打印配置;一次性 PHP 实验应输出源码指定的 PHP ready 后退出。--rm 自动删除这个临时容器,它不加入两服务项目。ps 应显示 db 为运行且健康、web 为运行;实际表格以本机 Compose 输出为准。

--wait 等待有健康检查的服务健康、其余服务运行。本例没有 web 健康检查,命令成功也不代表 WordPress 已安装。浏览器打开 http://127.0.0.1:8080/,确认进入安装向导;短暂未响应可稍后刷新。[7]

db 显示 healthy,但网页报“数据库连接失败”。你优先核对什么?

同一网络内的连接直接访问 db:3306,宿主机端口映射不参与这条路径。

绕过等待不会修复账号错误,还会增加初始化期间的连接失败。

探测通过说明本例的数据库就绪检查成功。它没有替 WordPress 登录,所以仍要核对应用连接配置,以及数据库卷里已存在的账号状态。

步骤 3 · 写入并重建

重建后读回同一篇文章

先写入你能认出的数据,再重建容器,才能验证持久化。文章正文和用户在 db_data 中;程序与上传文件在 wp_data 中。只留 Web 文件卷,不能保住数据库里的文章。[3] [4]

  1. 在安装向导里选语言,设置站点标题、管理员用户名、密码和邮箱。这个管理员是 WordPress 账号,与数据库的 wp 用户不同。保存登录信息。
  2. 登录后台,新增一篇文章,标题写 compose-persist-29,正文写 这篇文章来自第一次启动。,点击发布并确认发布完成。
  3. 打开文章页面,记录浏览器中的完整文章 URL。可选:在“媒体”里上传一张自己的小图片,记录它的文件 URL,用它检验上传文件卷。

在终端执行下方只读查询。SQL 是数据库查询语言;这条查询筛选出刚才发布的文章,避免把修订记录当成文章。

docker compose exec -T db sh -c '
  exec mariadb --user="$MARIADB_USER" --password="$MARIADB_PASSWORD" \
    --database="$MARIADB_DATABASE" --batch --skip-column-names \
    --execute="SELECT post_title FROM wp_posts
      WHERE post_type=\"post\" AND post_status=\"publish\"
      AND post_title=\"compose-persist-29\";"
'

预期验收(未实测):查询结果包含你刚发布的标题 compose-persist-29。没有结果时先检查是否真正发布、标题是否一致;不要继续重建。外层单引号让变量在 db 容器里展开;这些 MARIADB_* 值已由 Compose 传入。

现在运行普通 down,再 up:哪些东西会被移除?文章存在哪里,凭什么还在?

down 移除本项目的容器和默认网络;本例两个命名卷被保留。下一次同项目 up 创建新容器,再挂载原来的卷,数据库便能读回原文章。[8]

docker compose down
docker volume inspect web-db-lab_db_data web-db-lab_wp_data
docker compose up -d --wait --wait-timeout 180
docker compose ps

down 之后

web、db 容器:已移除
db_data + wp_data:保留

文章和文件仍在卷内;此时网页无法访问。

再 up 之后

新 web、db 容器:已创建

↓ 挂载原卷,读回已有数据

同一组 db_data + wp_data
图 4 被替换的是容器;文章和文件沿原卷延续。以上为预期状态示意,未实测。[8]

完成验收(未实测):volume inspect 应能找到两个原卷。重新运行上面的同一条 SQL 查询,应仍返回该标题。刷新已记录的文章 URL,核对正文,确认原管理员仍能登录;若上传过图片,也刷新其文件 URL。应直接看到原站点,而非新的安装向导。

深入一点:卷名为什么多了前缀?

配置里的 db_data 和 wp_data 是逻辑名。Compose 默认用项目名加前缀管理资源,所以本例实际卷名是 web-db-lab_db_data、web-db-lab_wp_data。固定 name: web-db-lab 有助于复用它们;-p 等更高优先级设置仍能改变项目名。项目名规则

卷持久化只覆盖本次容器移除与重建。它不能替代数据库备份,也不能抵挡误删卷或宿主机磁盘损坏。

步骤 4 · 排障与收尾

沿失败的环节排查

先区分“容器没起来”“数据库没就绪”和“应用连错配置”,再决定下一条命令。

docker compose ps -a
docker compose logs --tail=80 db web
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q db)"

最后一条用于 db 仍在运行时读取探测记录;若 ps -q db 为空,先根据 ps -a 和日志排查退出原因。向别人贴日志前,检查是否含有自己的敏感配置。

现象先看什么对应动作
8080 已占用端口映射创建失败换一个空闲的宿主机端口并重新 up;首次安装前确定端口,后续沿用。
db 不健康 / 等待超时db 日志与探测记录确认初始化进度、磁盘与配置;首次初始化确实较慢时,再调整宽限或等待时间。
改 .env 后认证失败数据库卷是否已有旧账号恢复原密码继续本实验;数据库已有数据时,初始化变量不会替你改账号密码。[4]
重建后又要安装项目名、卷名和挂载路径核对是否连接了新卷;保留原卷,先查找原项目,避免继续写入或误删。

你想释放容器和网络,明天继续编辑同一篇文章,应执行哪项?

-v 会删除本例声明的两个命名卷,明天无法靠它们恢复文章。

文章正文在数据库中;wp_data 中的程序和图片不能重建丢失的数据库。

普通 down 保留这两个命名卷,下一次同项目 up 可以复用。down -v 会同时删除本例的数据库和 Web 文件卷。[8]

日常收尾执行 docker compose down 即可。若已验收完成并确定丢弃本练习全部数据,才运行下方命令;它会删除文章、用户、设置和上传文件,且不会删除本地的 .env、compose.yaml。

# 破坏性操作:删除本项目容器、网络和两个数据卷
docker compose down -v
深入一点:db 运行途中坏了,会自动修好吗?

service_healthy 在启动依赖时等待条件;它不保证数据库永远可用。健康状态变为不健康,本身不会自动修复数据库,也不会自动重启 web。本例没有配置重启策略。运行期间的故障仍需应用重试、监控或人工排查。[6]

用自己的话解释:为什么“换了两个容器”仍能看到原文章和图片?

容器重新创建,但数据库和 Web 文件保存在容器外的命名卷里。同一项目的配置把原卷挂载回对应路径,因此可以读回之前的文章与上传文件。

  • 区分了容器和命名卷的生命周期
  • 提到 db_data 保存文章、wp_data 保存文件
  • 提到同项目复用原卷,且未使用 down -v

来源

核对日期:2026-11-03。Docker 行为以 Docker 官方文档为准;镜像入口行为参考镜像维护者说明。应用与命令均未实测。

  1. Docker:Compose 网络与服务名。
  2. Docker:变量替换、.env 和优先级。
  3. WordPress 官方镜像:数据库配置、文件卷与更新行为;本例镜像标签。
  4. MariaDB 官方镜像:初始化变量与已有数据目录;本例镜像标签。
  5. MariaDB:healthcheck.sh 的检测范围。
  6. Docker:依赖与启动顺序;健康检查配置。
  7. Docker:compose up 与 --wait;compose config。
  8. Docker:compose down 和 --volumes;项目名与优先级。
  9. Docker:端口发布与宿主机网络规则;docker run 与 --rm。