Docker 基础体系 · 第 66/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。
Docker 磁盘治理:Layer、Build Cache、Volume、日志和安全清理
Docker 磁盘占用并不是一个单一目录或一个单一对象造成的。一个正在运行的容器,至少可能同时涉及:
- 镜像的只读 Layer;
- 容器自身的可写 Layer;
- BuildKit 产生的构建缓存;
- Volume 或 bind mount 中的业务数据;
- 容器日志;
- Docker 网络、元数据以及临时文件。
这些对象的生命周期不同,删除条件也不同。删除镜像不一定会删除容器数据,删除容器也不一定会删除 Volume,清理 Build Cache 更不会自动清理日志。磁盘治理的第一原则是:先确认空间属于哪一种对象,再选择与其生命周期匹配的回收动作。
一、先建立磁盘空间模型
在 Linux 上,Docker Engine 通常由 dockerd 管理一个数据根目录。可以用以下命令查看:
docker info --format '{{.DockerRootDir}}'
典型输出可能是:
/var/lib/docker
Rootless Docker 的路径通常位于用户目录下,例如:
/home/alice/.local/share/docker
具体路径由安装方式、Rootless 配置和 Docker 版本决定,不能假设所有机器都是 /var/lib/docker。
从逻辑上看,Docker 主机上的占用可以近似表示为:
其中:
- :镜像的只读层;
- :容器可写层;
- :BuildKit 的中间层、缓存挂载和构建结果;
- :Docker Volume 使用的独立存储;
- :容器标准输出和标准错误日志;
- :容器、网络、镜像索引等管理数据。
这个公式不是 Docker 对外承诺的精确账单,而是诊断时的分类模型。不同对象可能共享底层数据块,因此把各项目录大小简单相加可能重复计算。
查看 Docker 能识别的主要对象占用:
docker system df
输出通常类似:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 12 4 4.8GB 2.1GB (43%)
Containers 8 3 320MB 100MB (31%)
Local Volumes 6 2 18.4GB 12.0GB (65%)
Build Cache 27 0 6.7GB 6.7GB
更详细地查看镜像和容器:
docker system df -v
需要注意两个边界:
docker system df主要统计 Docker 管理的镜像、容器、Volume 和构建缓存;- 它通常不会把日志作为一个独立的、完整的可回收类别展示。
因此,如果 docker system df 显示占用不高,但主机 df -h 已经接近满,日志、bind mount、文件系统保留空间、已删除但仍被进程打开的文件,都是重点怀疑对象。
二、Layer:镜像层、容器可写层和 Copy-on-Write
1. 镜像 Layer 是什么
Docker 镜像通常由多个只读 Layer 叠加组成。例如:
应用容器
├── 容器可写层
├── 应用依赖层
├── 系统工具层
└── 基础发行版层
Dockerfile 中很多会改变文件系统的指令,可能形成新的镜像层:
FROM debian:bookworm
RUN apt-get update && apt-get install -y curl
COPY app /opt/app
镜像层具有以下重要性质:
- 通常是不可变的;
- 可以被多个镜像共享;
- 可以被多个容器共同引用;
- 删除一个标签不等于删除所有对应 Layer;
- 只有当某个 Layer 不再被任何镜像或容器引用时,才可能回收其存储。
标签只是名称,例如:
myapp:latest
myapp:v1
两个标签可能指向同一个镜像对象,也可能指向不同对象。删除标签主要是删除引用,不一定立即释放 Layer。
2. overlay2 与 Copy-on-Write
在 Linux 上,现代 Docker Engine 常见的存储驱动是 overlay2。可以查看当前驱动:
docker info --format 'driver={{.Driver}} root={{.DockerRootDir}}'
overlay2 使用 OverlayFS 将多个目录合并成一个统一视图。简化表示如下:
merged = 容器看到的统一文件系统
upperdir = 容器可写层
lowerdir = 一个或多个镜像只读层
workdir = OverlayFS 工作目录
容器读取文件时,OverlayFS 先在 upperdir 查找,再向下查找 lowerdir。这就是“统一视图”。
当容器修改一个来自镜像层的文件时,会发生 Copy-on-Write,也就是“写时复制”:
- 文件原本只存在于某个只读镜像层;
- 容器执行写操作;
- OverlayFS 将文件复制到容器的可写层;
- 后续读写针对可写层中的副本进行;
- 原始镜像层仍保持不变。
例如,基础镜像中有一个 500 MB 的文件:
lowerdir/usr/share/data.bin = 500MB
容器只修改其中很小的一部分,实际使用的空间并不保证只增加修改部分。对普通文件而言,Copy-on-Write 可能需要把整个文件复制到 upperdir,随后再写入修改内容。于是容器可写层可能额外占用接近 500 MB。
这也是为什么“容器里只改了一行配置”并不一定意味着只增加几 KB。
3. 删除文件并不一定立即释放空间
如果容器删除镜像层中的文件,OverlayFS 通常通过 whiteout 等机制在上层记录“下层文件已被删除”:
lowerdir/etc/example.conf 存在
upperdir/中的 whiteout 标记 表示隐藏它
容器统一视图中的文件 不存在
但下层镜像本身没有被修改,文件内容仍然存在于镜像 Layer 中。删除容器内文件只能改变容器视图,不能压缩或改写镜像层。
因此:
docker exec <container> rm -rf /some/path
可能释放容器可写层中的数据,却不能释放该路径在镜像层中的空间。
4. 容器可写层不适合存放持久数据
容器可写层的生命周期通常绑定容器:
docker restart不会删除可写层;docker stop不会删除可写层;docker rm会删除该容器的可写层;docker compose down通常会删除由 Compose 创建的容器,从而删除其可写层;- 重新创建容器会得到新的可写层。
因此,数据库、上传文件、队列数据等不应依赖容器可写层。它们应放到 Volume 或外部存储中。否则重新部署或清理容器时,数据可能随容器一同消失。
三、为什么 du、df 和 Docker 统计可能不一致
诊断时至少需要同时看文件系统块和 inode:
df -h /var/lib/docker
df -i /var/lib/docker
示例:
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 100G 92G 8.0G 92% /
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 6553600 6551000 2600 100% /
第二种情况表示字节空间可能尚未完全用尽,但 inode 已经耗尽。此时创建新文件仍可能失败:
No space left on device
1. df 与 du 的含义不同
df统计文件系统已经分配的块;du遍历目录,统计当前仍可见的文件;- 一个文件被删除但仍被进程打开时,
df仍会计入其空间,而du看不到它。
可以在 Linux 上检查已删除但仍被打开的文件:
sudo lsof +L1
如果输出中存在 Docker 日志或容器相关文件,说明删除目录或文件名并不能释放空间,必须让持有文件描述符的进程关闭或重新打开该文件。对 Docker 日志而言,通常应通过重启或重新创建容器解决,而不是直接操作活动日志文件。
2. 不要直接修改 overlay2 内部目录
/var/lib/docker/overlay2 的目录结构属于存储驱动的内部实现。直接执行:
sudo rm -rf /var/lib/docker/overlay2/...
可能造成:
- 镜像元数据与实际 Layer 不一致;
- 容器启动失败;
- 共享 Layer 被误删;
- Docker daemon 无法正常加载对象;
- 数据恢复困难。
清理 Layer 必须使用 Docker 的对象引用关系进行回收,例如 docker image prune 或 docker system prune,不能把 overlay2 当作普通临时目录。
四、Build Cache:构建缓存和镜像 Layer 不是同一个概念
1. Dockerfile 缓存的基本过程
BuildKit 会为 Dockerfile 的步骤建立缓存。例如:
FROM node:22-bookworm
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
当第二次构建时:
FROM的基础镜像可以复用;COPY package*.json ./的输入未变,则该步骤可以复用;RUN npm ci的前置状态未变,则可以复用;- 如果最后的源码
COPY . .发生变化,只会使后续步骤失效。
这解释了为什么依赖文件应先复制、源码后复制:源码变化不会自动使依赖安装步骤失效。
缓存命中依赖于构建步骤的输入、指令和前置文件系统状态。修改 Dockerfile 中某个早期步骤,可能导致后续缓存全部失效。
2. Build Cache 与最终镜像的区别
最终镜像中的 Layer 是镜像的一部分;Build Cache 还可能包含:
- 尚未被最终镜像引用的中间结果;
- 不同构建阶段的中间层;
RUN --mount=type=cache创建的缓存目录;- 外部缓存导入或导出的记录;
- 多平台构建产生的中间对象。
例如:
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
/root/.cache/pip 的缓存数据用于加速后续构建,但不会自动进入最终镜像 Layer。删除构建缓存通常不会破坏已经构建完成的镜像,只会使后续构建变慢。
查看构建缓存:
docker buildx du
某些环境也可以使用:
docker builder prune
或:
docker buildx prune
具体可用参数以当前版本的帮助信息为准:
docker buildx prune --help
按时间清理一个较旧的缓存示例:
docker buildx prune --filter 'until=168h'
这里 168h 表示七天。清理前先观察缓存占用:
docker buildx du --verbose
BuildKit 可能同时被多个构建任务使用。正常的 prune 会由 BuildKit 处理引用关系,但在持续构建的 CI 主机上,过于激进的清理会造成:
- 缓存命中率下降;
- 构建时间增加;
- 网络下载和 CPU 消耗上升;
- 并发构建的性能抖动。
因此,Build Cache 的合理目标不是“始终为零”,而是控制其上限并回收长期未使用的记录。
3. 构建机与运行机的清理策略不同
构建机通常重视缓存命中率,适合保留近期缓存并按时间或容量清理。运行机通常不负责频繁构建,长期积累的 Build Cache 更可能属于可回收对象。
如果镜像通过外部 registry 作为发布介质,删除本机 Build Cache 不会删除 registry 中已经推送的镜像;但如果本机没有可重建的上下文、凭据或缓存,清理后恢复构建能力仍可能需要重新下载依赖。
五、Volume:持久数据的生命周期和最大风险
1. Volume 绕过容器可写层
Docker Volume 是由 Docker 管理的独立数据对象。挂载后,Volume 内的数据不再写入容器的可写 Layer:
容器进程
│
├── /app -> 容器可写层
└── /var/lib/db -> Docker Volume
因此,数据库写入 Volume 不会因为容器可写层被限制或删除而直接消失。删除容器通常不会自动删除 Volume,这正是 Volume 用于持久化的原因。
查看 Volume:
docker volume ls
docker volume inspect mydata
典型的 inspect 输出包含:
[
{
"Name": "mydata",
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/mydata/_data",
"Labels": {}
}
]
Mountpoint 是宿主机路径,但应用应通过 Docker 挂载使用它,而不是直接绕过 Docker 修改内部目录。
2. Named Volume、Anonymous Volume 和 bind mount
Named Volume:
docker volume create app-data
docker run -d \
--name app \
-v app-data:/var/lib/app \
myapp:1.0
Anonymous Volume:
docker run -d \
--name app \
-v /var/lib/app \
myapp:1.0
匿名 Volume 的名字由 Docker 生成,容易在长期运行的环境中积累。
bind mount 则直接使用宿主机路径:
docker run -d \
--name app \
-v /srv/app-data:/var/lib/app \
myapp:1.0
bind mount 不属于 Docker Volume,docker volume prune 不会清理它。其容量应使用宿主机工具诊断:
sudo du -xsh /srv/app-data
这三种方式不能混为一谈:
| 类型 | 数据位置 | 是否由 Docker Volume 管理 | volume prune 是否处理 |
|---|---|---|---|
| Named Volume | Docker 数据目录或驱动后端 | 是 | 可能处理未使用对象 |
| Anonymous Volume | Docker 数据目录或驱动后端 | 是 | 可能处理未使用对象 |
| bind mount | 宿主机指定路径 | 否 | 否 |
3. “未使用”不等于“没有价值”
Docker 的 Volume 回收判断主要依据 Docker 对象引用关系,例如没有容器引用它。它无法知道:
- Volume 中是否有备份;
- 是否有审计数据;
- 是否准备被下一个容器使用;
- 应用是否只是暂时停止;
- 数据是否已复制到外部系统。
因此,Volume 的“可回收”只表示“Docker 当前没有活跃引用”,不表示“业务上可以删除”。
列出当前容器的挂载关系:
docker ps -a \
--format 'table {{.ID}}\t{{.Names}}\t{{.Mounts}}'
检查具体容器:
docker inspect app \
--format '{{json .Mounts}}'
删除 Volume 前,应至少完成:
- 确认没有生产容器使用;
- 确认数据已备份或明确不需要;
- 确认恢复流程能够读取备份;
- 确认 Compose、systemd 或其他编排工具不会再次使用它。
六、容器日志:标准输出也会占满磁盘
1. 日志数据流
容器进程通常把日志写入标准输出和标准错误:
应用 stdout/stderr
│
▼
Docker logging driver
│
├── json-file
├── local
├── journald
├── syslog / fluentd / gelf ...
└── 其他驱动
日志驱动决定 Docker 如何接收、格式化、保存或转发日志。日志驱动不等于应用日志框架;应用仍然负责日志级别、结构化字段和敏感信息控制。
常见的 json-file 驱动会把每条日志包装成 JSON。其底层文件通常位于 Docker 数据根目录下的容器目录中,但具体路径属于实现细节,不应写死在运维脚本里。
检查 daemon 默认日志驱动:
docker info --format '{{.LoggingDriver}}'
检查某个容器实际使用的日志驱动:
docker inspect app \
--format '{{json .HostConfig.LogConfig}}'
2. 日志轮转配置
可以为 Docker daemon 配置默认日志驱动和轮转参数,例如 /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "5"
}
}
含义是单个日志文件达到约 100 MB 后轮转,最多保留 5 个文件。这里的总量大致上限是:
实际占用还会受到轮转时机、文件系统块分配和并发写入影响,所以这只是容量估算,不是精确保证。
修改 daemon 默认配置通常只影响之后创建的容器。已经存在的容器可能仍沿用创建时的日志配置,生产变更后应检查并按计划重新创建相关容器。
Compose 中可以为服务指定日志配置:
services:
api:
image: example/api:1.0
logging:
driver: json-file
options:
max-size: "100m"
max-file: "5"
Compose 配置是否生效,取决于容器是否被重新创建。仅执行普通的 docker compose restart 通常不会改变容器创建时的日志驱动配置;需要在变更后执行适当的重建或重新创建流程。
3. 其他日志驱动的边界
如果使用 journald、syslog 或远程日志驱动,日志可能主要存放在 Docker 之外:
journald由 systemd journal 的配额和轮转策略控制;- syslog 由宿主机 syslog 服务控制;
- 远程驱动的本地缓存、网络失败重试和下游不可用仍可能产生本地占用;
- 某些驱动不支持完整的
docker logs行为。
不能只看 Docker 数据目录来判断所有日志空间。应同时检查日志驱动和宿主机日志系统的配额。
4. 紧急日志处理的风险
不建议直接删除正在写入的日志文件:
sudo rm /path/to/container-json.log
进程可能仍持有已删除文件的文件描述符,结果是:
df仍显示空间没有释放;du看不到这个文件;- 日志记录状态异常;
- 后续排障失去关键证据。
生产环境应优先使用日志轮转、限制日志级别、外部日志系统或重新创建容器。若磁盘已经接近耗尽,临时释放空间必须由有经验的管理员执行,并立即验证:
df -h /
sudo lsof +L1
docker logs --tail 20 app
紧急操作不能替代长期轮转配置。
七、安全清理:删除对象不等于安全擦除秘密
Docker 的常规 prune 是空间回收,不是安全擦除。
如果敏感信息曾经进入以下对象:
- 镜像 Layer;
- Build Cache;
- Volume;
- 容器日志;
- bind mount;
- registry 或备份;
即使删除标签、容器或缓存,底层数据也可能因为快照、备份、远端副本、文件系统回收策略而继续存在。
1. 不要在 Dockerfile 中泄露秘密
错误示例:
ARG NPM_TOKEN
RUN npm config set //registry.example.com/:_authToken=$NPM_TOKEN
风险在于秘密可能进入:
- 构建命令记录;
- 镜像历史;
- 中间 Layer;
- Build Cache;
- CI 日志。
现代 BuildKit 支持使用 secret mount 的构建方式,例如:
DOCKER_BUILDKIT=1 docker build \
--secret id=npmrc,src="$HOME/.npmrc" \
-t example/app:1.0 .
Dockerfile:
# syntax=docker/dockerfile:1
FROM node:22-bookworm
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm ci
秘密在该 RUN 步骤中以临时挂载方式提供,不应被复制到最终镜像。仍需注意:
- 不要在命令中打印秘密;
- 不要把秘密写入构建产物;
- CI 日志可能记录失败命令或调试输出;
- 需要根据当前 BuildKit 和 Docker 版本验证 secret mount 支持。
如果秘密已经进入镜像或日志,正确处理通常是立即轮换凭据,再清理受影响对象;仅执行 prune 不能证明秘密已经从所有副本中消失。
2. 安全删除的实际含义
在 SSD、虚拟磁盘、写时复制存储、快照和 RAID 环境中,普通 rm 或 Docker prune 无法保证物理介质上的不可恢复擦除。
如果要求真正降低数据恢复可能性,应根据数据所在介质和组织策略采用:
- 磁盘或 Volume 加密;
- 加密密钥销毁;
- 云盘或存储后端提供的安全删除能力;
- 受控销毁备份、快照和 registry 副本。
这属于存储安全和合规问题,不应把 Docker 垃圾回收命令当作安全擦除工具。
八、各类清理命令及其边界
1. 清理已停止的容器
docker container prune
它清理已停止的容器,不清理正在运行的容器。容器被删除后,其可写层也会被删除,但挂载的 Named Volume 通常不会因此自动删除。
带过滤条件的示例:
docker container prune --filter 'until=168h'
这里按 Docker 支持的时间过滤语义选择较旧对象。执行前应理解当前版本的过滤规则:
docker container prune --help
2. 清理悬空镜像
docker image prune
通常清理没有标签且不再被引用的镜像层。它比下面的命令保守:
docker image prune -a
-a 会扩大到所有未被现有容器使用的镜像。生产环境中,一个镜像可能当前没有容器引用,但仍是回滚版本或离线恢复所需对象,因此不能仅以“现在没运行”判断是否可删。
3. 清理未使用 Volume
docker volume prune
Volume 清理是高风险操作。不同 Docker 版本对默认行为和 -a 选项的支持可能存在差异,应先查看:
docker volume prune --help
在支持的版本中,-a 会扩大清理范围,可能包含未使用的 Named Volume:
docker volume prune -a
不要把以下两者混为一谈:
docker compose down
docker compose down -v
前者通常删除 Compose 创建的容器和网络,但保留声明的 Volume;后者会删除项目关联的非外部 Volume。external: true 的外部 Volume 通常不由 Compose 删除,但仍应以当前 Compose 版本和配置验证。
4. 清理构建缓存
docker buildx prune --filter 'until=168h'
或者使用传统 builder 接口:
docker builder prune --filter 'until=168h'
先看占用:
docker buildx du --verbose
构建缓存删除后,镜像通常仍可运行,但下一次构建需要重新执行被淘汰的步骤。
5. 组合清理命令的风险
docker system prune
通常会清理一组未使用对象,但默认不会删除 Volume。扩大范围:
docker system prune -a --volumes
这可能同时涉及:
- 已停止容器;
- 未使用网络;
- 未使用镜像;
- 构建缓存;
- 未使用 Volume。
它不是“自动安全清理”,而是多个高影响动作的组合。生产环境不应把它放进没有审计、没有过滤条件、没有备份验证的定时任务中。
九、一个可审计的清理流程
下面是一套适合人工执行的流程。它的重点不是命令数量,而是每一步都保留判断证据。
第一步:确认哪个文件系统满了
df -hT
df -i
如果 /var/lib/docker 位于根分区,则应关注根分区;如果 Docker 根目录位于单独挂载点,则单独检查该挂载点。
第二步:确认 Docker 数据根目录和驱动
docker info --format \
'root={{.DockerRootDir}} driver={{.Driver}} logging={{.LoggingDriver}}'
这一步能避免误把另一块磁盘或 bind mount 当成 Docker 内部空间。
第三步:分类统计
docker system df
docker system df -v
docker ps -a --size
docker volume ls
docker buildx du
docker ps -a --size 可以辅助发现容器可写层增长异常,但不能代表 Volume 或日志占用。
第四步:定位宿主机目录
对 Docker 数据根目录执行只读分析:
sudo du -xhd1 "$(docker info --format '{{.DockerRootDir}}')" \
2>/dev/null | sort -h
-x 避免跨越到其他文件系统。若发现 Docker 内部目录很大,应回到 Docker 对象层面确认,而不是直接删除目录。
同时检查 bind mount:
sudo du -xhd1 /srv 2>/dev/null | sort -h
第五步:先清理低风险对象
例如清理明确已停止且超过保留期的容器:
docker container prune --filter 'until=168h'
再按计划清理旧 Build Cache:
docker buildx prune --filter 'until=168h'
每完成一类清理,就重新确认:
df -h
docker system df
第六步:处理日志和 Volume
如果日志是主要占用,应修改轮转策略并重新创建受影响容器,而不是直接删除活动日志文件。
如果 Volume 是主要占用,应按业务数据清单逐个核对。可以先导出备份,例如对一个测试 Volume:
docker run --rm \
-v app-data:/source:ro \
-v "$PWD":/backup \
alpine \
tar czf /backup/app-data.tar.gz -C /source .
恢复测试:
docker volume create app-data-restore
docker run --rm \
-v app-data-restore:/target \
-v "$PWD":/backup \
alpine \
tar xzf /backup/app-data.tar.gz -C /target
这两个命令分别把 Volume 内容打包到宿主机当前目录,再解压到新 Volume。真实生产备份还应考虑一致性:数据库需要使用数据库自身的备份机制或在合适状态下执行快照,不能把正在变化的数据库目录简单打包就认为是可恢复备份。
第七步:清理后验证服务行为
空间释放后,还要验证:
docker ps
docker compose ps
docker logs --tail 50 <container>
df -h
df -i
如果之前发生过 inode 耗尽、数据库写满、日志丢失或容器重建,必须进一步检查应用健康状态、数据完整性和告警恢复情况。
十、自动化回收必须处理并发和故障路径
磁盘清理通常与以下操作并发发生:
- CI 正在构建镜像;
- 发布系统正在拉取镜像;
- Compose 正在重建容器;
- 应用正在写日志;
- 备份任务正在读取 Volume;
- 运维人员同时手动执行 prune。
Docker daemon 会维护对象引用关系,正常的 Docker 命令不会简单删除仍被运行容器引用的镜像层。但业务层面的竞态仍然存在。例如:
- 运维脚本判断某个 Volume 当前未被容器挂载;
- 部署系统在下一秒创建新容器并准备使用它;
- 清理脚本删除 Volume;
- 新容器启动时得到空数据目录或直接失败。
因此自动化流程应使用明确的标签、项目范围和保留策略,而不是依赖“当前看起来没有引用”。例如为临时构建资源设置标签,并在创建和清理时保持同一套命名规则。对于 Compose 项目,清理前应确认项目、外部 Volume 和部署控制器的实际关系。
一个安全的状态转换可以表示为:
flowchart TD
A[发现磁盘告警] --> B[确认文件系统与 DockerRootDir]
B --> C[区分 Layer / Cache / Volume / Log / bind mount]
C --> D{是否有业务数据或活动引用?}
D -- 是 --> E[停止写入或迁移数据]
E --> F[备份并验证恢复]
F --> G[按对象类型清理]
D -- 否 --> G
G --> H[重新检查 df / inode / system df]
H --> I[验证容器、日志和应用健康状态]
I --> J[记录清理结果与保留策略]
图中的“备份并验证恢复”不是所有镜像或缓存都需要执行,但对 Volume 和包含敏感信息的对象,不能省略业务判断。
十一、常见误解和对应的失败表现
误解一:删除容器就会删除所有数据
实际情况:
- 容器可写层通常会删除;
- Named Volume 通常保留;
- bind mount 数据保留;
- 镜像 Layer 保留;
- 日志是否删除取决于容器日志文件及其生命周期。
因此,删除容器后磁盘没有明显下降并不矛盾。
误解二:删除镜像标签就释放了镜像空间
标签只是引用。只要其他标签、容器或缓存仍然引用相同 Layer,数据就不能回收。应通过:
docker system df -v
观察共享关系和可回收空间,而不是只看 docker image ls 的标签数量。
误解三:容器内 rm 可以清理镜像空间
容器内删除操作只改变容器视图,并可能增加 whiteout 元数据。它不能修改下层镜像。要缩小镜像,应修改 Dockerfile、减少不必要文件、在同一构建步骤中清理包管理器缓存,并重新构建镜像。
例如下面的写法容易把包索引保留在镜像层:
RUN apt-get update
RUN apt-get install -y curl
更合理的方式是让索引下载、安装和清理处于同一个 Layer:
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
这不是对所有发行版和软件包管理器都通用的固定模板,但其因果关系是明确的:只有在同一 Layer 中删除,删除结果才不会被前一个 Layer 的内容继续占用。
误解四:df 满了就清理镜像
如果真正占用空间的是:
/var/lib/docker/containers/...下的日志;- bind mount;
- Volume;
- inode;
- 已删除但仍打开的文件;
清理镜像可能几乎没有效果。必须先分类。
误解五:prune 就是安全擦除
prune 只解除 Docker 对象并回收可回收空间,不负责清除快照、备份、registry 副本或 SSD 上的物理残留。秘密一旦泄露,首要动作是凭据轮换,其次才是按范围清理和验证。
十二、生产环境中的容量告警边界
容量告警至少应覆盖三类指标:
- 文件系统字节使用率;
- inode 使用率;
- Docker 对象和应用数据增长率。
只监控 df -h 可能漏掉 inode 耗尽,只监控 docker system df 又可能漏掉 bind mount 和日志。
还应区分:
- Docker 数据根目录所在文件系统;
- Volume 所在的外部存储;
- bind mount 所在文件系统;
- journald 或远程日志系统;
- registry、构建缓存和 CI 工作目录。
容量告警最好在“还有恢复余量”时触发,而不是等到写操作已经失败后才执行清理。告警内容应包含挂载点、使用率、inode 使用率、最近增长量和可能的最大目录,便于直接进入分类诊断。
结语
Docker 磁盘治理的关键不是记住某一条 prune 命令,而是理解对象之间的生命周期:
- Layer 由镜像和容器引用关系决定;
- 容器可写层随容器存在,但不适合持久化;
- Build Cache 服务于构建速度,删除后通常影响性能而不是运行结果;
- Volume 保存业务数据,Docker 判断“未使用”不代表业务上可以删除;
- 日志由 logging driver 管理,轮转必须在容器创建配置和宿主机日志系统两侧确认;
- 安全清理不等于 Docker 垃圾回收,秘密泄露需要凭据轮换和存储范围治理。
正确的操作顺序始终是:确认文件系统,分类对象,识别引用关系,备份业务数据,按风险执行清理,再验证空间和服务状态。
系列导航与关联阅读
- 系列入口:Docker 完整学习路线:从镜像与容器到安全、可观测和生产交付
- 上一篇:Docker 离线与受限网络:镜像同步、依赖缓存、签名和补丁
- 下一篇:Docker 网络故障诊断:Namespace、Bridge、DNS、NAT、MTU 和抓包
- 延伸:Docker Storage Driver:overlay2、Copy-on-Write、Inode 和磁盘诊断
- 延伸:Docker 日志与监控:Logging Driver、Metrics、事件和容量告警
官方资料
本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。

评论
0 条讨论