把参数搬进配置树
命令变成服务
Compose 是定义和管理多容器应用的工具。项目(project)是一组由它一起管理的资源;服务(service)是创建容器的配置,本例每个服务运行一个容器。[1]
左边逐条创建资源,右边描述同样的运行配置。为避免名称冲突,左边用 runlab 前缀,右边用项目名 cfile。先读对照;动手时只执行后面的实验。
逐条执行 docker run
docker network create runlab_default
docker volume create runlab_site
docker run -d --name runlab-web \
--network runlab_default \
--network-alias web \
-p 127.0.0.1:8080:80 \
-v runlab_site:/usr/share/nginx/html:ro \
nginx:alpine
docker run -d --name runlab-client \
--network runlab_default \
--network-alias client \
busybox:1.37 sleep 86400
网络别名 --network-alias web 让同网络的容器能用 web 找到它;第二个容器保持运行,供实验发请求。此对照未实测。
保存为 compose.yaml
name: cfile
services:
web:
image: nginx:alpine
ports:
- "127.0.0.1:8080:80"
volumes:
- site:/usr/share/nginx/html:ro
client:
image: busybox:1.37
command: ["sleep", "86400"]
networks:
default: {}
volumes:
site: {}
{} 表示使用默认配置。default 是 Compose 的默认网络键;这里把通常可以省略的声明写出来,方便看清结构。[3]
YAML 用缩进表达层级:用空格,不用 Tab。web、client 缩进在 services 下;image 再缩进一层。- 开头表示列表项,端口字符串加引号。
| docker run 的部分 | Compose 的位置 | 含义 |
|---|---|---|
nginx:alpine | services.web.image | 用哪个镜像 |
-p 127.0.0.1:8080:80 | services.web.ports | 宿主机回环地址的 8080 → 容器的 80 |
-v …:/usr/share/nginx/html:ro | services.web.volumes | 挂载数据,ro 表示只读 |
镜像后的 sleep 86400 | services.client.command | 覆盖镜像默认命令,等待 86400 秒 |
-d | docker compose up -d | 后台运行,属于这次启动命令的选项 |
Compose 会按项目生成容器名,无需照搬 --name。服务名写在 services 下即可。镜像标签引用自官方镜像页;标签可能更新,本例不承诺镜像大小或具体构建版本。[10]
把配置放回正确的层级。顶层声明资源,服务内说明怎样使用它。
- 声明 site 卷
- 发布 8080:80
- command: ["sleep", "86400"]
- 声明 default 网络
- 挂载 site 到网页目录
顶层 networks、volumes 定义资源;服务配置决定谁使用它们。容器启动参数集中到对应服务下,-d 留在启动命令中。
深入一点:为什么没写 version?
现代 Compose 按 Compose Specification 解析文件。旧教程中的 version: "3.8" 已过时,并不用于选择解析规则;本例直接省略。name: cfile 指定项目名,命令行 -p 或环境变量 COMPOSE_PROJECT_NAME 可以覆盖它。本实验不使用这两种覆盖。[5]
名字和端口都带作用范围
同网用服务名
没有显式指定其他网络的服务,会加入项目的 默认网络(default network)。本例项目名为 cfile,网络名默认是 cfile_default,由 Compose 创建。[2]
域名系统(Domain Name System,DNS)在这里把服务名解析成容器地址。同一网络里的 client 可访问 http://web:80;你在宿主机访问 http://127.0.0.1:8080。即使移除 ports,这两个服务仍能走共同网络通信。
| 请求从哪里发出 | 地址 | 请求路径 |
|---|---|---|
| client 容器内 | http://web:80 | 服务名 → web 容器端口 |
| Docker 所在宿主机 | http://127.0.0.1:8080 | 宿主机发布端口 → web:80 |
| client 容器内 | http://localhost:8080 | localhost 指 client 自己,本例没有监听 |
仍用页首的图:从 client 请求 web,填入主机名和端口。
http://:
同网络内用服务名 web 和容器端口 80。8080 是宿主机入口,容器之间不需要绕行这个入口。
深入一点:服务名、容器名、hostname
“服务名就是主机名”指的是同网络中可用于访问的 DNS 名。它不表示在容器内运行 hostname 一定打印 web。hostname: 是另一个可单独配置的属性;container_name: 则是容器对象的名字。本页都无需设置。容器重建后 IP 可能变化,访问者应继续用服务名查找。[1] [2]
只把端口映射改成 "127.0.0.1:9090:80"。client 该访问哪里?
9090 是宿主机发布端口,web 容器仍监听 80。
client 中的 localhost 回到 client 自己。
映射变化只改变宿主机入口。同网络内仍直接访问 web:80,并不经过宿主机的 9090。
深入一点:自定义网络也要“声明 + 连接”
需要明确命名时,可把原文件中的 networks 改成下面三处;其他服务字段照旧。只声明 app 不会自动把所有服务接入它;显式选择 app 的服务也不会自动再加入 default。[3]
services:
web:
# image、ports、volumes 保持原样
networks: [app]
client:
# image、command 保持原样
networks: [app]
networks:
app: {}这只是局部修改示意,不是可独立启动的完整文件。只有共享网络的服务才能用该网络上的服务名互通;本例实际创建的网络名是 cfile_app。
同名字段,两个层级
卷要声明和挂载
命名卷(named volume)是由 Docker 管理、独立于容器生命周期的数据存储。顶层 volumes.site 声明卷;services.web.volumes 把它挂载到容器目录。少了挂载,web 就用不到这个卷。[4]
site:/usr/share/nginx/html:ro 把项目卷接到 web 的目录上,供它只读访问。默认情况下,项目名会作为卷名前缀:配置键 site 对应实际卷 cfile_site。docker compose down 删除容器和项目网络,默认保留命名卷;再次 up 会复用它。[4] [7]
你只在顶层写了 volumes: {site: {}},删掉 web 内的 volumes。web 还能从这个卷读网页吗?为什么?
不能。顶层只声明资源,没有把卷接到 web 的文件系统。要读取它,web 仍需要 volumes: ["site:/usr/share/nginx/html:ro"]。另外,ro 只限制这次挂载,不会把整个卷永久变成只读。
深入一点:site 和 ./site 不一样
site:/usr/share/nginx/html 使用命名卷,需要顶层声明;./site:/usr/share/nginx/html 使用绑定挂载(bind mount),把宿主机目录接进去,不需要顶层卷声明。单文件实验中,相对路径从 compose.yaml 所在目录解析。别为了修缩进随手加 ./,那会改变存储类型。[1]
只运行这一条实验路线
启动到清理
目标:写入一份网页,让两个入口都能读取;删除容器后,再验证卷中的文件仍在。需要 Linux 容器模式、可用的 Docker 与 Compose v2,以及拉取镜像的网络。命令在 Docker 所在的本机终端运行。
实验会拉取镜像、创建项目网络和卷,并发布本机 127.0.0.1:8080。Linux Engine 的桥接网络和端口发布可能由 Docker 调整宿主机防火墙规则;Docker Desktop 在其虚拟机中管理容器网络。请使用空闲端口及未使用的 cfile 项目名。不需 --privileged,也不需手工改 iptables。
- 保存配置,用
config -q校验 YAML。 up -d创建并启动服务,终端返回后容器继续运行;再向卷写入测试网页。ps查看本项目的服务状态与发布端口。- 发请求,再用
logs web读取 web 的标准输出和错误输出。先预测:下一步 down 会删掉网页数据吗? down删除项目容器和网络,命名卷保留。- 再次
up -d复用卷;再请求网页,验证内容保留。
down 的清理边界不包含命名卷。[6] [7] [8]1. 保存并校验文件
新建空目录 compose-structure-lab 并进入。把前面的完整 YAML保存成 compose.yaml。后续命令都留在此目录,不运行左栏的 runlab 命令。
预期(未实测):config -q 校验成功时无输出。它只校验配置,不启动服务。[9]
2. 启动、写入并检查
预期(未实测):ps 列出 web、client 正在运行,web 发布本机 8080。两次 HTTP 请求的正文都应等于刚写入 index.html 的那一行文本。web 日志应能找到访问记录;具体格式、时间和容器名以实机为准,待维护者补录。
先由 up 创建 cfile_site,再用临时容器挂载并写入网页。--rm 删除临时容器,命名卷仍在;web 的只读挂载不妨碍临时容器通过自己的可写挂载更新文件。[11]
exec client … 在已运行的 client 容器内执行命令;BusyBox 提供此处使用的 wget。curl 在宿主机运行,没有它可直接用浏览器打开地址。up -d 返回不保证应用已经就绪;若首次请求过早失败,先查看 ps、logs web,再重试。依赖与健康检查留给下一页。
docker compose logs -f web 可持续跟随日志,按 Ctrl+C 只退出日志查看;后台服务继续运行。ps -a 还会列出已停止的容器。[8]
3. 删除容器,验证数据
预期(未实测):down 后 ps -a 不再列出本例服务容器;临时容器仍能读出你写入的文本。重新启动后的 HTTP 正文保持相同。若请求过早,同样先检查状态再重试。
确认不再需要实验网页后,在此目录运行 docker compose down -v。这会删除容器、项目网络,以及文件中声明的 cfile_site 命名卷;镜像和本地 YAML 保留。此命令也未实测。不要把 -v 当成查看详情的参数。[7]
卡住时先定位哪一层
config -q失败:检查缩进、冒号以及卷是否声明。- 8080 已被占用:把端口映射改为
"127.0.0.1:18080:80",重新up -d;宿主机改访问 18080,client 仍访问 web:80。 - 服务退出或网页不可读:用
ps -a和logs web检查;确认初始化命令成功、卷名为cfile_site。 - 服务名无法解析:确认你在 client 内执行,并且两个服务连接同一网络。
离开前再检索一次
四条带走
services把镜像和运行参数归到各自服务下。- 顶层声明网络、卷;服务内连接网络、挂载卷。
- 同网访问用服务名和容器端口;宿主机用发布端口。
up -d启动,ps查状态,logs读日志,down清理容器与网络。
你想删除本例两个容器和网络,同时下次继续使用原网页。哪条命令合适?
-v 会删除文件中声明的命名卷,原网页也随之丢失。
logs 只读取服务输出,不删除任何资源。
普通 down 删除服务容器和项目网络,保留命名卷。项目名仍为 cfile、挂载仍指向 site 时,下次 up 会复用它。
向同伴解释:为什么 YAML 顶层和 web 内都有 volumes,却不算重复?
顶层声明可用的命名卷,服务内把某个卷挂到自己的目录。一个回答“有什么资源”,另一个回答“谁在哪里使用”。
- 提到顶层声明和服务内挂载的区别
- 能以 site 与 /usr/share/nginx/html 为例
- 能解释普通 down 后数据为什么仍在
来源
官方文档核对日期:2026-11-03。Docker 命令均未实测;未给出容器 ID、日志时间等虚构记录。
- Docker Docs:Services — 服务结构、image、command、ports、hostname、挂载语法。
- Docker Docs:Networking in Compose — 默认项目网络、服务名解析与容器端口。
- Docker Docs:Networks — 隐式 default 与显式网络连接。
- Docker Docs:Volumes — 顶层卷声明和服务挂载、复用现有卷。
- Docker Docs:Version and name、Compose CLI — version 过时、项目名与覆盖优先级。
- Docker Docs:docker compose up — 创建、重建和后台运行。
- Docker Docs:docker compose down — 默认清理范围、-v;external 资源不被删除。
- Docker Docs:ps、logs、exec — 状态、日志与容器内执行。
- Docker Docs:config — 校验并展开配置;-q 只校验。
- NGINX 官方镜像、BusyBox 官方镜像 — nginx:alpine 和 busybox:1.37 标签与用法。
- Docker Docs:Engine volumes — 命名卷生命周期、只读挂载与临时容器。
- Docker Docs:Packet filtering and firewalls — Linux bridge 网络和端口发布的防火墙规则。