Docker 基础体系 · 第 22/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。
Docker CLI 与对象模型:Container、Image、Network、Volume 和 Filter
Docker CLI 不是直接操作“容器进程”的一组快捷命令,而是通过 Docker Engine API 请求 Docker daemon 管理一组具有明确生命周期和引用关系的对象。本文以现代 Docker Engine、BuildKit 和 Compose 规范为基础,讨论 Linux 容器场景下最重要的五类对象:
- Image:用于创建容器的不可变内容模板;
- Container:某个 Image 的一次可运行实例,包含配置、状态和可写层;
- Network:连接容器与外部网络的虚拟网络对象;
- Volume:由 Docker 管理的持久化数据对象;
- Filter:由 CLI 传递给对象查询命令的筛选条件。
理解这些对象之间的关系,比记住几十条命令更重要。很多看似相似的操作,例如“删除镜像”“删除容器”“删除数据”,实际作用于不同对象,失败原因也不同。
一、CLI 操作的真实对象:客户端、Daemon 与上下文
在 Linux 主机上执行:
docker ps
通常经历如下路径:
docker CLI
│
│ Docker Engine API
▼
docker daemon
│
├── containerd
├── runc
├── 镜像存储与快照
├── 网络驱动
└── Volume 驱动
CLI 负责解析命令行参数、选择 Docker context、发起 API 请求并格式化输出。真正创建容器、挂载文件系统、配置网络和维护对象元数据的是 daemon。
1. Docker context 决定操作哪一个 Engine
docker context ls
docker context show
输出可能类似:
NAME DESCRIPTION DOCKER ENDPOINT
default * Current DOCKER_HOST based configuration unix:///var/run/docker.sock
default 通常通过 Unix socket 连接本机 daemon。也可以切换到远程 Engine:
docker context use production
docker ps
此时 docker ps 展示的是远程主机上的容器,而不是当前开发机上的容器。
因此,以下两个命令的语义可能完全不同:
docker image ls
docker --context production image ls
前者查询当前 context 指向的 Engine,后者查询 production context 指向的 Engine。排查“为什么我刚刚创建的容器不见了”时,首先应确认:
docker context show
docker info
docker info 还可以确认 Server 端的存储驱动、容器数量、镜像数量、架构和根目录等信息。
2. CLI 参数与 daemon 配置不是同一层
例如:
docker run --name web nginx:alpine
--name web 是本次创建请求中的容器配置。
而以下配置通常属于 daemon 或 Engine 环境:
- 镜像存储目录;
- 默认日志驱动;
- 默认网络;
- cgroup 与内存限制能力;
- rootless 模式;
- 远程 API 监听地址。
CLI 可以请求资源,是否能成功仍取决于 daemon 的权限、内核能力、存储驱动和网络环境。Linux 容器的隔离最终依赖 Linux namespaces、cgroups、挂载和网络机制;Docker CLI 本身不提供隔离。
二、对象关系:Image、Container、Network 和 Volume 如何组合
一个典型的 Web 容器可以抽象为:
graph LR
I[Image nginx:alpine] --> C[Container web]
C --> W[可写容器层]
C --> E[Network Endpoint]
E --> N[Network app-net]
C --> M[Mount]
M --> V[Volume web-data]
N --> P[宿主机端口 127.0.0.1:8080]
这张图中有几个容易混淆的关系:
- Image 不是 Container。Image 是模板,Container 是实例。
- Network 不是 Container 内部的网络命名空间本身,而是管理连接关系的对象;容器加入网络时会创建网络 endpoint。
- Volume 不是 Image 的一层,也不是容器的可写层。
- 端口发布不是“把容器加入网络”的同义词,而是配置宿主机到容器网络的转发规则。
- Container 删除通常不会自动删除 Image;Volume 是否删除则取决于删除命令和 Volume 类型。
可以使用 inspect 查看对象之间的实际引用:
docker inspect web
docker network inspect app-net
docker volume inspect web-data
docker image inspect nginx:alpine
普通 ls 命令适合发现对象,inspect 才适合确认对象的详细配置和引用关系。
三、Image:可复用的内容模板
3.1 Image 的定义
Docker Image 是由元数据和文件系统层组成的内容集合。它通常包含:
- 基础文件系统;
- 环境变量默认值;
- 默认工作目录;
- 默认用户;
ENTRYPOINT;CMD;- 暴露端口声明;
- Volume 声明;
- 构建历史和标签信息。
Image 本身不是一个正在运行的进程,也没有“启动”这一状态。
例如:
docker pull nginx:alpine
docker image ls nginx
可能看到:
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx alpine 3f8a00f137a0 ... ...
这里的 nginx:alpine 是一个标签引用,不是镜像内容本身。标签可以被重新指向新的镜像摘要,因此它不是不可变标识。
更稳定的标识是 digest:
docker image inspect nginx:alpine \
--format '{{json .RepoDigests}}'
可能输出:
["nginx@sha256:..."]
使用 digest 运行:
docker run --rm nginx@sha256:<digest>
表示请求该内容摘要对应的镜像,而不是当前标签可能指向的版本。
标签与摘要的区别
可以把它们抽象为:
标签:nginx:alpine ─────► 当前某个镜像摘要
摘要:sha256:abc... ────► 固定内容
标签是可变的名称映射,digest 是内容寻址结果。执行:
docker pull nginx:alpine
可能使本地标签更新到新的镜像内容,但已有容器不会因此自动替换根文件系统。容器创建时使用的是当时解析到的 Image 内容,除非显式重新创建容器。
3.2 镜像层、可写层和容器文件系统
通过 Dockerfile 构建镜像:
FROM alpine:3.20
RUN apk add --no-cache curl
COPY app.sh /usr/local/bin/app.sh
ENTRYPOINT ["/usr/local/bin/app.sh"]
使用 BuildKit 构建:
DOCKER_BUILDKIT=1 docker build -t demo:1 .
每个能够产生文件系统变化的构建步骤通常会形成可复用的层。BuildKit 还维护构建缓存,并可以并行执行独立步骤,但最终镜像仍表现为由若干只读层组成的文件系统视图。
启动容器时,Docker 通常在 Image 只读层之上增加一个容器专属的可写层:
Image layer 1 只读
Image layer 2 只读
Image layer 3 只读
-------------------
Container writable layer
容器内执行:
echo changed > /tmp/example
写入的是容器可写层,而不是 Image。执行:
docker rm container-name
删除容器时,这个可写层通常也会消失。
因此,以下命令不会把容器运行期间写入的普通文件保存为新镜像:
docker stop web
docker start web
停止和重新启动同一个容器会保留其可写层;删除后再用同一个 Image 创建新容器,则不会保留这些修改。
如果确实需要将当前容器文件系统状态制作成新镜像:
docker commit web web:snapshot
但 docker commit 更适合调试或临时捕获状态,不适合作为可审计的常规构建流程。Dockerfile 和 BuildKit 构建过程能明确记录输入、步骤和产物,commit 则容易隐藏修改来源。
可写层不是持久化数据库
即使容器停止后可写层仍然存在,也不应把它当作数据库持久化方案:
- 容器删除后通常丢失;
- 存储位置受 Engine 存储驱动管理;
- 备份和迁移不如 Volume 明确;
- 写时复制可能使频繁修改大文件产生额外开销。
需要跨容器或跨生命周期保存的数据,应使用 Volume 或明确的 bind mount。
3.3 Image 的查询与删除
查看本地镜像:
docker image ls
docker image ls --all
docker image ls 展示的是本地标签视图,多个标签可能指向同一个 Image ID。--all 会显示中间层或没有常规标签的镜像记录,但它仍不是底层所有存储层的逐层磁盘报告。
查看镜像详细信息:
docker image inspect nginx:alpine
常见字段包括:
Id:镜像内容标识;RepoTags:本地标签;RepoDigests:仓库摘要;Config:默认配置;RootFS:根文件系统层信息;Architecture、Os:平台信息。
删除标签或镜像:
docker image rm nginx:alpine
如果镜像仍被容器引用,删除可能失败:
conflict: unable to delete ... image is being used by running container
即使容器已停止,只要容器对象仍引用该 Image,也可能需要先删除容器。docker image rm -f 可以强制删除标签或镜像引用,但它不会把正在运行的容器“改成没有镜像”;运行中的容器仍需要其已有 rootfs 资源。
四、Container:Image 的一次实例化结果
4.1 创建、启动与运行不是同一个动作
以下命令可以拆成两个阶段:
docker create --name web nginx:alpine
docker start web
docker create 创建 Container 对象,但不启动其主进程。docker start 才启动已经存在的容器。
而:
docker run --name web nginx:alpine
大致等价于:
解析 Image
↓
创建 Container 配置、可写层、挂载和网络连接
↓
启动容器主进程
↓
等待或后台返回
Container 的生命周期可以简化为:
stateDiagram-v2
[*] --> Created: docker create
Created --> Running: docker start/run
Running --> Exited: 主进程退出或 stop
Exited --> Running: docker start
Running --> Paused: docker pause
Paused --> Running: docker unpause
Created --> Removed: docker rm
Exited --> Removed: docker rm
Running --> Removed: docker rm -f
Removed --> [*]
容器状态不是 Image 的状态,也不是宿主机进程状态的简单别名。Docker 记录容器的生命周期、退出码、启动时间、停止时间、健康检查结果等元数据。
查询运行中容器:
docker container ls
查询所有状态的容器:
docker container ls -a
默认 docker ps 只显示运行中的容器,是常见误解的来源。一个“没有显示”的容器可能只是已经退出。
4.2 主进程决定容器何时退出
例如:
docker run --name once alpine:3.20 sh -c 'echo hello'
sh -c 'echo hello' 很快退出,因此容器状态会变成 Exited。
查看:
docker ps -a --filter name=once
docker inspect once --format \
'status={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}}'
可能得到:
status=exited exit=0 error=
退出码为 0 只表示该主进程正常退出,不表示业务一定完成了所有预期工作;非零退出码表示进程以错误状态退出,但具体原因仍需结合日志和容器内进程行为判断。
长时间运行的容器通常需要一个持续运行的前台进程:
docker run -d --name sleeper alpine:3.20 sleep 3600
-d 只让 CLI 在后台运行并返回容器 ID,不会改变容器中进程的前台/后台语义。容器的生命周期仍由其主进程决定。
4.3 CMD、ENTRYPOINT 与命令覆盖
假设镜像配置为:
ENTRYPOINT ["python"]
CMD ["app.py"]
那么:
docker run app-image
等效于执行:
python app.py
而:
docker run app-image worker.py
通常会把 CMD 替换为 worker.py,形成:
python worker.py
如果需要完全覆盖入口:
docker run --entrypoint sh app-image
这组规则属于 Image 配置与 docker run 参数的合并结果。排查“容器启动后立即退出”时,应检查:
docker image inspect app-image
docker container inspect app
docker logs app
不要只看 Dockerfile,因为运行命令、Compose 配置和 CLI 参数都可能覆盖默认值。
4.4 start、restart、rm 和 exec 的边界
docker start
启动已经存在的容器:
docker start web
它不会重新读取最新的 Image 标签,也不会重新创建挂载、端口和网络配置。
docker restart
docker restart web
通常是停止后再次启动同一个容器,仍然保留容器对象和可写层。它不是“用最新镜像滚动更新”。
docker rm
docker rm web
删除已停止容器。删除运行中容器需要:
docker rm -f web
强制删除会终止容器进程,可能中断正在进行的请求或写入。删除容器不会默认删除它使用的命名 Volume。
docker exec
docker exec -it web sh
exec 在已经运行的容器中启动额外进程。它不是重新进入原来的 shell,也不是启动新的容器。这个进程共享目标容器的 namespaces,因此通常可以看到同一个网络、挂载和进程视图。
如果容器不是运行状态,docker exec 会失败。调试时应先确认:
docker ps --filter name=web
docker inspect web --format '{{.State.Status}}'
极简 Image 可能没有 bash、ps、ip 或调试工具,因此:
docker exec -it web bash
可能失败,而:
docker exec -it web sh
可能成功。调试工具不存在不等于容器网络或进程不存在。
五、Network:连接关系、Endpoint 与端口发布
5.1 Network 对象解决什么问题
Docker Network 是一组网络配置和连接关系。容器加入某个 Network 后,Docker 为该容器创建对应的 endpoint,并将其接入网络驱动管理的网络空间。
查看网络:
docker network ls
典型网络包括:
bridge:默认桥接网络;host:Linux 上使用宿主机网络命名空间的特殊模式;none:不配置常规网络连接;- 用户创建的 bridge、overlay 等网络。
创建用户定义桥接网络:
docker network create app-net
启动两个容器:
docker run -d --name redis --network app-net redis:7-alpine
docker run --rm --network app-net redis:7-alpine \
redis-cli -h redis ping
第二个容器将 redis 作为目标主机名。用户定义的 bridge 网络通常提供容器名称和网络别名解析;这比依赖默认 bridge 网络中的旧式链接机制更清晰。
redis-cli 成功得到:
PONG
其因果路径是:
redis 容器加入 app-net
↓
网络驱动为容器配置网络接口和地址
↓
Docker 内置 DNS 将 redis 解析到该网络地址
↓
redis-cli 连接 redis:6379
查看具体连接:
docker network inspect app-net
其中的 Containers 字段显示加入网络的容器、IPv4/IPv6 地址和 endpoint 信息。
5.2 网络连接与端口发布不是一回事
启动服务:
docker run -d \
--name web \
--network app-net \
-p 127.0.0.1:8080:80 \
nginx:alpine
--network app-net 表示容器加入 app-net。
-p 127.0.0.1:8080:80 表示配置宿主机地址和端口到容器端口的发布关系:
宿主机 127.0.0.1:8080
↓ 转发
容器 web 的 80/tcp
它们解决不同问题:
- 同一 Docker 网络中的容器访问
web:80,不需要宿主机发布端口; - 宿主机访问
127.0.0.1:8080,依赖端口发布; - Image 中的
EXPOSE 80只是元数据声明,不自动让宿主机端口可访问; - 如果没有
-p,容器仍可能被同一网络中的其他容器访问。
验证:
curl http://127.0.0.1:8080
如果宿主机端口已被占用,创建或启动容器可能失败:
Bind for 127.0.0.1:8080 failed: port is already allocated
恢复方式不是修改容器运行状态,而是选择空闲宿主机端口、停止占用者,或重新创建容器。端口发布属于容器创建配置,不能用 docker start 重新读取新的 -p 参数。
5.3 容器加入和离开网络
创建容器时加入网络:
docker run -d --name api --network app-net my-api:1
也可以对已有容器进行连接:
docker network connect app-net api
docker network disconnect app-net api
一个容器可以同时连接多个 Network:
docker network connect frontend api
docker network connect backend api
这使容器可能同时拥有多个网络接口和多个地址。网络隔离边界由“容器是否连接到同一个网络”以及网络驱动的配置共同决定,而不是由容器名称决定。
删除网络:
docker network rm app-net
如果仍有容器连接,通常会失败。正确顺序是:
docker network disconnect app-net api
docker network rm app-net
或者先删除相关容器。强制清理网络前应确认没有生产流量,因为网络删除会立即破坏依赖该网络的连接。
5.4 Linux 上的 host 和 none
docker run --rm --network host nginx:alpine
Linux 容器使用 host 网络模式时,容器进程使用宿主机网络命名空间,容器端口与宿主机端口不再是两个独立网络栈中的映射关系。此模式削弱网络隔离,应明确评估监听地址、端口冲突和权限影响。
docker run --rm --network none alpine:3.20 ip addr
none 网络模式只保留回环等有限接口,不提供通常的外部网络连接。它适合验证程序是否意外依赖网络,但具体可见接口仍取决于运行时和镜像工具。
这些模式是 Linux 容器边界下的实现与平台行为,不能直接推导为所有 Docker Desktop 平台上的宿主机等价行为。Docker Desktop 中容器通常运行在 Linux VM 内,宿主机访问路径还包含虚拟机网络层。
六、Volume:独立于容器可写层的数据对象
6.1 Volume 的基本模型
命名 Volume 是 Docker 管理的数据对象:
docker volume create web-data
docker volume inspect web-data
其关键属性通常包括:
Name:Volume 名称;Driver:Volume 驱动;Mountpoint:daemon 主机上的实际挂载位置;Scope:本地或其他作用域;Labels:标签元数据;Options:驱动选项。
在容器中挂载:
docker run -d \
--name web \
-v web-data:/usr/share/nginx/html \
nginx:alpine
容器内的 /usr/share/nginx/html 不再只由 Image 内容提供,而是由 Volume 挂载覆盖。这里发生的是挂载遮蔽:
Image 中 /usr/share/nginx/html 的内容
↓ 被 Volume mount 覆盖
容器进程看到 Volume 中的目录
如果容器删除,命名 Volume 默认仍然存在:
docker rm -f web
docker volume ls
重新创建容器并挂载同一个 web-data,可以继续看到其中的数据。
6.2 Volume、bind mount 与 tmpfs
Docker 常见挂载形式包括:
命名 Volume
docker run --rm \
--mount type=volume,src=web-data,dst=/data \
alpine:3.20
数据由 Docker Volume 驱动管理,适合需要明确生命周期和 Docker 对象管理的数据。
Bind mount
docker run --rm \
--mount type=bind,src="$PWD/config",dst=/etc/myapp,readonly \
my-app:1
宿主机路径直接映射进容器。它适合开发时共享源代码或注入配置,但依赖宿主机目录结构、权限和 SELinux/AppArmor 等环境。
tmpfs
docker run --rm \
--tmpfs /run:rw,noexec,nosuid,size=64m \
alpine:3.20
数据保存在内存或内核管理的临时文件系统中,容器停止后不作为持久化数据保留。具体可用参数受 Linux 和 Engine 支持限制。
长格式 --mount 通常更明确:
--mount type=volume,src=web-data,dst=/data,readonly
短格式 -v 更简洁,但路径、冒号和读写选项混在一起,复杂场景更容易写错。
6.3 Volume 挂载会遮蔽 Image 内容
假设镜像中有:
/usr/share/nginx/html/index.html
把一个空 Volume 挂载到该目录后,容器内是否立即为空,取决于 Docker 对空 Volume 的初始化行为和挂载配置。对于常见 Docker Volume,Docker 通常会把挂载点原有内容复制到新建的空 Volume;使用 volume-nocopy 可以关闭这一行为:
docker run --rm \
--mount type=volume,src=web-data,dst=/usr/share/nginx/html,volume-nocopy \
nginx:alpine
但一旦完成挂载,容器进程看到的是 Volume 视图,而不是同时合并 Image 目录和 Volume 目录。Volume 中已有文件不会与 Image 中的文件进行普通目录合并。
这也是“文件明明在镜像里,挂载后却找不到”的常见原因。
6.4 Volume 删除与数据风险
删除容器:
docker rm web
默认不会删除命名 Volume。
删除 Volume:
docker volume rm web-data
如果仍有容器使用它,通常会失败:
volume is in use
必须先停止并删除引用该 Volume 的容器,或移除挂载关系。删除 Volume 通常意味着删除数据,不能把它当成清理元数据的无害操作。
docker rm -v 的行为也需要精确理解:
docker rm -v web
它会尝试删除容器关联的匿名 Volume;对于命名 Volume,通常不会因为这个参数而自动删除。命名 Volume 的删除应显式执行:
docker volume rm web-data
生产环境中删除前应先确认:
docker volume inspect web-data
docker ps -a --filter volume=web-data
备份 Volume 也不应假定可以直接复制 daemon 的内部目录。更可靠的方式是启动临时容器,将 Volume 挂载到 /source,将备份目标挂载到 /backup,使用归档工具读取文件。
七、Filter:对象查询的筛选语言
7.1 Filter 的定义
--filter 用于限制查询或选择命令的对象集合。例如:
docker ps --filter status=running
docker image ls --filter dangling=true
docker volume ls --filter dangling=true
Filter 不是 SQL,也不是一个对所有 Docker 子命令都完全统一的表达式语言。每个命令支持的键、值格式和匹配对象由该命令定义。
必须区分:
docker ps --filter name=web
和:
docker image ls --filter reference=nginx
前者筛选 Container,后者筛选 Image 引用。name=web 不能直接推导为“所有名字包含 web 的 Docker 对象”。
过滤与格式化不是一回事
docker ps --filter status=exited \
--format '{{.ID}}\t{{.Names}}\t{{.Status}}'
这里:
--filter决定返回哪些对象;--format决定如何显示返回结果。
如果要在脚本中进一步处理,建议使用稳定字段或 JSON:
docker inspect --format '{{json .}}' web
不要依赖人类可读的 STATUS 文本进行精确解析,因为输出格式可能包含时间、健康状态和版本相关文本。
7.2 Container 常用 Filter
查看所有退出状态的容器:
docker ps -a --filter status=exited
只看非零退出的容器:
docker ps -a \
--filter status=exited \
--filter "exited=1"
按名称筛选:
docker ps -a --filter name=web
按标签筛选:
docker ps -a --filter "label=app=demo"
按网络筛选:
docker ps -a --filter network=app-net
按 Volume 筛选:
docker ps -a --filter volume=web-data
按祖先镜像筛选:
docker ps -a --filter ancestor=nginx:alpine
这里的 ancestor 表示容器使用的镜像祖先关系,不能简单理解为容器当前文件内容与标签字符串做文本比较。
按创建时间相对筛选:
docker ps -a --filter before=web
docker ps -a --filter since=web
这些条件通常以某个容器的创建时间为边界,而不是以容器最近启动时间为边界。清理脚本若误把“很久没运行”理解为“很久没创建”,可能删除错误对象。
docker ps 中的常用状态包括:
createdrunningpausedrestartingexiteddead
查询结果为空并不代表 daemon 没有对象,可能只是所有对象不满足当前筛选条件。
7.3 Image、Network 和 Volume 的 Filter
Image
docker image ls --filter dangling=true
docker image ls --filter "reference=nginx:*"
docker image ls --filter "label=stage=builder"
dangling=true 通常用于查找没有仓库标签直接引用的镜像记录:
docker image prune
但 dangling 镜像仍可能被构建缓存、调试流程或某些容器引用。生产清理前应结合:
docker ps -a
docker image inspect <image>
docker system df
Network
docker network ls --filter driver=bridge
docker network ls --filter scope=local
docker network ls --filter label=project=demo
docker network ls --filter dangling=true
dangling 网络通常指没有容器连接的网络,但实际筛选定义由 docker network ls 支持的条件决定。
Volume
docker volume ls --filter dangling=true
docker volume ls --filter driver=local
docker volume ls --filter name=web
docker volume ls --filter label=project=demo
Volume 查询结果为空,不代表主机没有业务数据;数据也可能位于 bind mount、远程驱动或容器可写层中。
7.4 Filter 的组合、引用和安全边界
多个过滤条件通常用于缩小结果集,但不能把所有命令都假定为同一种布尔语义。尤其是同一个键重复出现时,是否表示多个候选值、是否允许多个值、是否按 AND 或 OR 解释,应以目标子命令的文档和实际版本为准。
例如,不要未经验证地把下面命令理解为“状态是 exited 且 running”:
docker ps -a \
--filter status=exited \
--filter status=running
更可控的方式是使用一次查询和明确的脚本逻辑,或分别查询后合并结果。
Filter 也不会绕过权限和对象引用检查:
docker rm $(docker ps -aq --filter "label=app=demo")
这条命令存在几个风险:
- 没有匹配结果时,命令替换可能导致
docker rm参数不足; - 标签可能被错误地复用;
- 容器可能仍在运行;
- 删除容器可能导致服务中断;
- 容器删除后,命名 Volume 仍可能保留,造成“对象已清理但数据仍在”。
较安全的步骤是先预览:
docker ps -a \
--filter "label=app=demo" \
--format 'table {{.ID}}\t{{.Names}}\t{{.Status}}'
确认后再执行删除:
docker ps -aq --filter "label=app=demo" |
xargs -r docker rm -f
这仍然是破坏性操作,xargs 只是避免空输入,并不能替代人工确认和业务保护。
八、完整算例:创建、查询、调试和清理一组对象
下面建立一个最小但完整的对象关系:
- Image:
nginx:alpine; - Container:
demo-web; - Network:
demo-net; - Volume:
demo-data; - Label:
app=demo; - 宿主机端口:
127.0.0.1:8080。
8.1 准备对象
docker network create --label app=demo demo-net
docker volume create --label app=demo demo-data
预期输出分别是网络名和 Volume 名:
demo-net
demo-data
然后创建容器:
docker run -d \
--name demo-web \
--label app=demo \
--network demo-net \
-p 127.0.0.1:8080:80 \
--mount type=volume,src=demo-data,dst=/usr/share/nginx/html \
nginx:alpine
这里的因果顺序是:
- 解析本地的
nginx:alpine,若不存在则尝试拉取; - 创建
demo-webContainer; - 为 Container 创建可写层;
- 将
demo-data挂载到指定目录; - 将 Container 连接到
demo-net; - 配置宿主机端口转发;
- 启动 Image 的默认入口进程;
-d让 CLI 返回后不等待进程退出。
验证容器:
docker ps \
--filter name=demo-web \
--format 'table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}'
预期类似:
CONTAINER ID NAMES STATUS PORTS
... demo-web Up ... 127.0.0.1:8080->80/tcp
验证 HTTP:
curl -I http://127.0.0.1:8080
预期看到类似:
HTTP/1.1 200 OK
Server: nginx/...
版本号和完整响应头可能不同,不应把它们当作固定输出。
8.2 查询引用关系
查询容器的核心状态:
docker inspect demo-web --format '
status={{.State.Status}}
image={{.Config.Image}}
network={{json .NetworkSettings.Networks}}
mounts={{json .Mounts}}
'
查询网络中的容器:
docker network inspect demo-net
查询 Volume:
docker volume inspect demo-data
查询带标签的全部对象:
docker ps -a --filter "label=app=demo"
docker network ls --filter "label=app=demo"
docker volume ls --filter "label=app=demo"
注意标签不是跨对象自动继承的。给 Container 加上:
--label app=demo
不会自动给它使用的 Network 或 Volume 加标签。因此上面的网络和 Volume 必须在创建时分别设置标签。
8.3 修改 Volume 中的数据
在临时容器中写入 Volume:
docker run --rm \
--mount type=volume,src=demo-data,dst=/data \
alpine:3.20 \
sh -c 'echo "managed by volume" > /data/index.html'
然后访问 Web 容器:
curl http://127.0.0.1:8080/index.html
预期:
managed by volume
这里的文件不在 demo-web 的可写层,而在 demo-data 中。删除并重新创建 Web 容器,只要继续挂载相同 Volume,文件仍会存在:
docker rm -f demo-web
docker run -d \
--name demo-web \
--label app=demo \
--network demo-net \
-p 127.0.0.1:8080:80 \
--mount type=volume,src=demo-data,dst=/usr/share/nginx/html \
nginx:alpine
curl http://127.0.0.1:8080/index.html
如果执行:
docker volume rm demo-data
则会删除这个数据对象,后续重新创建同名 Volume 时不会自动恢复原文件。
8.4 故障路径:对象存在但服务不可用
情况一:容器存在但已退出
docker ps
没有结果不代表没有容器。执行:
docker ps -a --filter name=demo-web
docker logs demo-web
docker inspect demo-web --format \
'status={{.State.Status}} exit={{.State.ExitCode}} error={{.State.Error}}'
如果容器状态是 exited,先看主进程退出原因,而不是重复执行 docker start。
情况二:网络不存在
docker run --rm --network missing-net alpine:3.20 true
会因网络对象不存在而失败。需要先创建或使用正确的 context:
docker network ls
docker context show
情况三:端口冲突
docker run -d -p 127.0.0.1:8080:80 nginx:alpine
若 8080 已被占用,创建可能失败。验证宿主机监听者:
ss -ltnp | grep ':8080'
然后选择其他端口,例如:
docker run -d -p 127.0.0.1:8081:80 nginx:alpine
情况四:Volume 删除失败
docker volume rm demo-data
如果 demo-web 仍挂载该 Volume,Engine 会拒绝删除。先检查引用:
docker ps -a --filter volume=demo-data
docker inspect demo-web --format '{{json .Mounts}}'
正确顺序通常是:
docker rm -f demo-web
docker volume rm demo-data
docker network rm demo-net
九、Docker Compose 与底层对象模型
Compose 文件描述的是一组服务及其依赖关系,但启动后仍然会创建 Docker Engine 的基础对象。
示例:
services:
web:
image: nginx:alpine
ports:
- "127.0.0.1:8080:80"
networks:
- app
volumes:
- web-data:/usr/share/nginx/html
networks:
app:
volumes:
web-data:
启动:
docker compose up -d
Compose 通常会创建:
- 一个或多个 Container;
- 一个项目级 Network;
- 一个命名 Volume;
- 对应的端口、挂载和标签元数据。
查询时可以同时使用 Compose 和原生 CLI:
docker compose ps
docker ps --filter label=com.docker.compose.project
docker network ls
docker volume ls
Compose 的服务名、项目名和容器实际名称不是同一个概念。应用连接时应优先使用 Compose 网络中的服务名,而不是手工推断生成的容器名。
web 服务名
↓ Compose DNS
同一网络中的 web 地址
↓
访问容器内部端口
docker compose down 默认会停止并删除由该项目创建的容器和网络,但通常保留命名 Volume。执行:
docker compose down -v
会额外删除该 Compose 项目管理的 Volume,因此可能导致数据库数据丢失。这个命令应被视为破坏性清理,而不是普通的“重启项目”。
十、对象模型中的几个关键边界
10.1 删除容器不等于删除镜像
docker rm demo-web
只删除 Container。Image nginx:alpine 仍然存在,可以继续创建新容器。
反过来,删除 Image 也不会自动删除所有使用它的 Container;如果存在引用,Engine 通常拒绝删除,避免破坏对象关系。
10.2 重启容器不等于更新镜像
docker restart demo-web
不会拉取新镜像,也不会重新创建容器。要使用新 Image,通常需要:
docker pull nginx:alpine
docker rm -f demo-web
docker run ...
或者使用 Compose:
docker compose pull
docker compose up -d
Compose 会根据配置决定是否重新创建受影响的容器,但 Volume 和网络的保留策略仍需单独理解。
10.3 EXPOSE 不等于 -p
Dockerfile:
EXPOSE 8080
只是声明镜像预期使用的容器端口。它不会创建宿主机监听端口。宿主机访问能力需要:
docker run -p 127.0.0.1:8080:8080 image-name
而同一 Docker 网络中的容器访问容器端口,不一定需要端口发布。
10.4 Container 可写层不等于 Volume
容器内执行:
echo x > /tmp/x
默认写入 Container 可写层。
执行:
echo y > /data/y
如果 /data 是 Volume 挂载点,则写入 Volume。
可以通过:
docker inspect container-name --format '{{json .Mounts}}'
确认一个路径是否位于挂载对象中,而不能只凭容器内路径名称猜测。
10.5 Filter 不等于安全策略
Filter 只筛选查询结果或命令目标。它不会提供:
- 访问控制;
- 审批;
- 数据备份;
- 事务回滚;
- 运行中连接的保护。
因此:
docker system prune
或带有 -a、--volumes 的清理命令必须先理解会影响哪些对象。过滤和清理是两个不同阶段:先枚举、再验证、最后执行破坏性操作。
十一、推荐的诊断顺序
当“容器无法访问”时,可以按对象边界逐层检查。
1. 确认目标 Engine
docker context show
docker info
避免在错误的远程主机上操作。
2. 确认 Container 状态
docker ps -a --filter name=demo-web
docker inspect demo-web --format \
'{{.State.Status}} {{.State.ExitCode}} {{.State.Error}}'
如果不是 running,先检查:
docker logs demo-web
3. 确认端口发布
docker port demo-web
docker inspect demo-web --format '{{json .HostConfig.PortBindings}}'
确认宿主机地址、端口和容器端口是否符合预期。
4. 确认网络连接
docker network inspect demo-net
docker inspect demo-web --format '{{json .NetworkSettings.Networks}}'
确认容器确实连接到目标 Network,并检查容器 IP、别名和 endpoint。
5. 确认挂载
docker inspect demo-web --format '{{json .Mounts}}'
docker volume inspect demo-data
确认应用实际读取的目录没有被错误的 Volume 或 bind mount 遮蔽。
6. 最后进入容器或执行诊断进程
docker exec demo-web sh
进入后检查应用进程、监听地址和文件是否存在。若镜像没有诊断工具,可以使用同一 Network 的临时工具容器,而不是为了调试永久修改生产镜像。
Docker CLI 的命令表面上以动词组织:
run、start、stop、rm、inspect、exec、network、volume
但其真实语义由对象模型决定:
Image 提供内容与默认配置
Container 保存实例配置、状态和可写层
Network 管理连接关系与地址路径
Volume 管理独立数据生命周期
Filter 限制对象查询与选择范围
掌握这五个对象及其引用关系后,docker run 不再是一个需要死记参数的黑盒命令,而可以被还原为一组明确的状态变化:解析 Image、创建 Container、建立挂载、连接 Network、配置端口、启动主进程,并在后续通过 Filter、Inspect、Logs 和 Exec 观察每一层是否满足预期。
系列导航与关联阅读
- 系列入口:Docker 完整学习路线:从镜像与容器到安全、可观测和生产交付
- 上一篇:Docker 生产交付体系:CI、灰度、回滚、容量和运行手册
- 下一篇:Docker Inspect、Exec 与调试:Namespace、环境、进程和最小镜像
- 延伸:Docker 与 OCI 架构:CLI、Daemon、containerd、runc 和隔离边界
官方资料
本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。

评论
0 条讨论