镜像到底存在哪:/var/lib/docker 的四级映射与 daemon.json 配置

docker image ls 只告诉你「有什么」,不告诉你「在哪」。真出问题时------磁盘满了、镜像层找不到、pull 卡住不动------必须能顺着目录一路找到文件本身。这篇把 /var/lib/docker 的两区结构、overlay2 的四个子目录、从镜像名到磁盘目录的四级映射讲清,再补上 daemon.json 里那几个决定「能不能拉、能不能被连」的字段。

本文概要:

项目 内容
文章主题 Docker 镜像在本地的存储结构(元数据区与层数据区、overlay2 四目录、四级映射链)、空间清理的边界,以及 daemon.json 与 systemd 覆盖的配合
所属类别 云计算 → 容器技术 → Docker 存储与守护进程配置
知识关键字 /var/lib/docker · overlay2 · cache-id · repositories.json · daemon.json · ExecStart · containerd image store

主线分两半。前半是「文件在哪」 :目录结构加映射链,用来定位和排查。后半是「配置怎么生效」:daemon.json 与 systemd 覆盖,用来解决拉不动、连不上。

两半的接点在 docker info:它一次就把存储驱动、镜像根目录、仓库配置全告诉你。

一、两个区:一个记索引,一个存文件

1.1 先确认存储驱动

镜像都放在 /var/lib/docker/ 下,但具体怎么组织由存储驱动(Storage Driver)决定 。StorageDriver 不是一个真实目录名,它是变量------实际会被替换成 overlay2、vfs、btrfs 之类。所以第一步永远是:

bash 复制代码
[root@docker ~]# docker info | grep "Storage Driver"
 Storage Driver: overlay2

绝大多数现代 Linux 上都是 overlay2,下面全部按它讲。

1.2 整体结构

路径 装什么 能不能手删
overlay2/ 镜像层与容器层的实际文件(大头) 不能
image/overlay2/ 元数据:镜像名、镜像 ID、层 ID 的对应关系 不能
containers/ 每个容器的配置与日志 不能,用 docker rm
volumes/ 数据卷的内容 不能,用 docker volume rm
buildkit/ 构建缓存 可以用 docker builder prune

一句话分工:元数据区是目录,层数据区才是书。

二、overlay2 的四个子目录

每个层在 overlay2/ 下对应一个以随机 cache-id 命名的目录,里面有四样东西:

子目录 / 文件 作用
diff/ ★ 这一层真实的文件内容(增删改)。注意它只含本层改动,不是完整文件系统
link 一个短名称,给 lower 引用,避免路径过长
lower 该层依赖的下层列表
work/ OverlayFS 内部工作目录,做 copy-up 时用,不用管也不用清

merged 是另一个容易混淆的目录:它只在容器运行时存在 。它的内容是把只读层(lowerdir)与可写层(upperdir)联合挂载后的合并视图------你在容器里看到的 / 就是它。还没运行过的镜像没有 merged 目录,这不是镜像缺失。

📌 记住一句:diff 是「这一层改了什么」,merged 是「合起来长什么样」。前者一直在,后者用完就拆。

三、四级映射:从 centos:8 找到磁盘目录

这条路要走四跳,因为四个标识互不相同:

第一跳:镜像名 → 镜像 ID

bash 复制代码
[root@docker ~]# cat /var/lib/docker/image/overlay2/repositories.json
{"Repositories":{"centos":{"centos:8":"sha256:eeb6ee3f..."}}}

第二跳:镜像 ID → 层 ID 列表

image/overlay2/imagedb/content/sha256/<镜像ID>(文件名是去掉 sha256: 前缀的那串)里,RootFS.Layers 按顺序列出构成该镜像的所有层 ID。

bash 复制代码
[root@docker ~]# docker image inspect centos:8 --format '{{json .RootFS.Layers}}'

第三跳:层 ID → cache-id

这是最关键的一步:overlay2 的目录名不等于层 ID ,而是一个随机 cache-id。layerdb/sha256/<层ID>/cache-id 这个文件的内容,就是该层在 overlay2/ 下的实际目录名。

第四跳:cache-id → 文件

/var/lib/docker/overlay2/<cache-id>/diff/ 就是这一层的真文件。

一条命令走完四跳:

bash 复制代码
[root@docker ~]# for l in $(docker image inspect centos:8 --format '{{json .RootFS.Layers}}' | jq -r '.[]'); do
>   cat /var/lib/docker/image/overlay2/layerdb/sha256/${l#sha256:}/cache-id; done
7e78d40a938c39b51b84a87cd56bf1baea6a8fbfd4c252fda16c494b0155cf32

拿到这个值,拼上 overlay2/ 就能 du -sh 看这层占多少:

bash 复制代码
[root@docker ~]# du -sh /var/lib/docker/overlay2/7e78d40a938c.../diff

⚠️ 注意 :${l#sha256:} 是去掉前缀的意思。少写这一段会找不到文件,报 No such file or directory------不是层坏了,是路径拼错了。

四、清空间:先摸底,再动手

绝对不要手删 /var/lib/docker/ 下的文件。 那里的内容完全由 dockerd 管理,手改会破坏元数据与层数据的一致性,轻则镜像列表错乱,重则整个 daemon 起不来。

正确的顺序是「先看、后清」:

bash 复制代码
# 1) 先看各类资源各占多少
[root@docker ~]# docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          5         2         517MB     379MB (73%)
Containers      2         1         1.2kB     1.2kB (100%)
Local Volumes   0         0         0B        0B
Build Cache     0         0         0B        0B

# 2) 看一级子目录谁最大(排查时用,别拿来删)
[root@docker ~]# du -h --max-depth=1 /var/lib/docker/overlay2 | sort -rh | head
命令 清什么 风险
docker image prune 悬空镜像(<none>) 零风险
docker builder prune 构建缓存 零风险,下次构建慢一点
docker image prune -a --filter "until=168h" 7 天前创建且无容器引用的镜像 可控
docker system prune 停止的容器、悬空镜像、无用网络 中
docker system prune -a --volumes 上面全部 + 所有未使用镜像 + 数据卷 破坏性最强,最后手段

docker system df 的 RECLAIMABLE 那一列才是真正能腾出来的空间,别拿 du 的结果当账。

五、daemon.json:四个字段决定能不能用

bash 复制代码
[root@docker ~]# cat /etc/docker/daemon.json
{
  "registry-mirrors": ["https://abcdef123456.mirror.swr.myhuaweicloud.com"],
  "insecure-registries": ["hub.zhb.cloud"],
  "hosts": [
    "unix:///var/run/docker.sock",
    "tcp://0.0.0.0:2375"
  ]
}
字段 管什么 不配的后果
registry-mirrors 拉公共镜像时的加速站,按顺序尝试 直连 docker.io,国内基本拉不动
insecure-registries 允许用 HTTP 访问的私有仓库 私有 Harbor 只开 HTTP 时报 http: server gave HTTP response to HTTPS client
hosts daemon 监听哪些地址 只监听本地 unix socket,别的机器连不上
log-driver / log-opts 容器日志驱动与大小上限 日志无限增长,把磁盘写满

前两个在《Docker 架构与部署》的 3.5 里讲过用法。registry-mirrors 放加速站,这里填的是华为云 SWR 的加速地址;insecure-registries 放只开 HTTP 的自建 Harbor 域名。两个字段不能互换。

这里重点说 hosts------它是最容易配了不生效的一个。

5.1 hosts 为什么会被 ExecStart 顶掉

发行版的 docker.service 里,启动命令是写死的:

复制代码
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

-H fd:// 是从命令行指定监听地址。命令行参数和 daemon.json 的 hosts 同时存在时,docker 会直接启动失败 (报 unable to configure the Docker daemon with file /etc/docker/daemon.json: the following directives are specified both as a flag and in the configuration file: hosts)。所以想让 hosts 生效,必须先把命令行里的 -H 去掉。

正解是用 systemd drop-in 覆盖,不动原文件:

bash 复制代码
[root@docker ~]# systemctl edit --force docker.service

在打开的编辑器里写:

ini 复制代码
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd --containerd=/run/containerd/containerd.sock

ExecStart= 那一行是空的,这是关键。 systemd 的规则是:同一指令第二次赋值视为追加,只有先赋一个空值才能把原值整体清掉。少了这行,两个 ExecStart 会拼在一起,还是冲突。

📌 另一种做法是直接编辑 /usr/lib/systemd/system/docker.service 里的 ExecStart,把 -H 加在命令行上,完整写法见《Docker 架构与部署》的 4.1。两种做法效果等价,区别只在文件归属:那份单元文件属于 docker-ce 包,yum update 一次就被装回原样,所以长期维护的机器用本文这套 drop-in,一次实验用那种也不妨。

5.2 让配置生效

bash 复制代码
[root@docker ~]# systemctl daemon-reload
[root@docker ~]# systemctl restart docker
[root@docker ~]# lsof -i :2375
dockerd  2565  root  3u  IPv6  76021  0t0  TCP *:2375 (LISTEN)
[root@docker ~]# docker info | grep -A2 "Registry Mirrors"

daemon-reload 重读 unit 文件,restart 才让新参数生效。只做 restart 不做 daemon-reload,改的 override.conf 不会被读到。

⚠️ 注意 :hosts 配错(比如写成 tcp://127.0.0.1:2375 却期待远程能连)时,docker 不会报错,只是连不上。判据永远是 lsof -i :2375 里显示的是 *:2375 还是 127.0.0.1:2375。

六、一个容易被忽略的变化:containerd image store

以上全部针对传统的 overlay2 graph driver 。从 Docker Engine 29.0 起,全新安装默认改用 containerd image store 。存储路径也随之变成 /var/lib/containerd ,与 /var/lib/docker 完全独立。

两个实际影响:

  • 占更多空间:containerd store 会同时保留压缩层和解压层,同样的镜像可能比 overlay2 模式大不少;
  • 路径对不上 :所有按 /var/lib/docker/overlay2 写死的脚本、巡检项、清理任务都会失效。

从旧版本升级上来的机器会继续用 overlay2,除非手动启用 containerd store。所以看到 /var/lib/docker/overlay2 里有正常内容,说明还在经典模式------这也是判断环境类型最快的办法。

bash 复制代码
# 看当前用的是哪种镜像存储
[root@docker ~]# docker info | grep -i "storage driver"

七、镜像拉不下来时的另一条路:proxychains-ng

镜像加速站也会挂。想让单条命令 走代理而不影响整机,用 proxychains-ng:它通过 LD_PRELOAD 拦截程序发出的连接,转发到代理。

bash 复制代码
# CentOS 7 要先启用 EPEL 源
[root@docker ~]# yum install -y epel-release
[root@docker ~]# yum install -y proxychains-ng

# 在 /etc/proxychains.conf 末尾的 [ProxyList] 里加一行
[root@docker ~]# vim /etc/proxychains.conf
[ProxyList]
socks5 127.0.0.1 1080
# 或 http 代理
# http 192.168.1.10 8080

# 任何命令前加 proxychains4 就强制走代理
[root@docker ~]# proxychains4 curl ifconfig.me
[root@docker ~]# proxychains4 docker pull nginx

⚠️ 注意 :proxychains 只代理 TCP ,ICMP 不走------所以 ping 依然不通,别拿 ping 判断代理有没有生效。用 curl 或 docker pull 验证。

八、排错速查

现象 大概率原因 处理
docker info 里没有 Storage Driver 行 服务没起 systemctl status docker
overlay2/xxx/diff: No such file 层 ID 前缀没去掉,路径拼错 用 ${l#sha256:} 去掉前缀
镜像列表里有 <none> 占空间 悬空镜像 docker image prune
hosts 配了但只有本地能连 ExecStart 里的 -H fd:// 没清 用 drop-in 覆盖,ExecStart= 先赋空
改完配置毫无变化 只 restart 没 daemon-reload,或根本没重启 systemctl daemon-reload && systemctl restart docker
私有仓库报 http: server gave HTTP response to HTTPS client 仓库只开 HTTP,没进白名单 加进 insecure-registries
磁盘莫名少了十几 GB 容器日志没限大小 / 构建缓存堆积 配 log-opts,跑 docker builder prune

📚 参考资料(官方文档)

附:术语英文全称速查表

缩写 / 名词 英文全称 中文
Storage Driver Storage Driver 存储驱动
graph driver graph driver 图形驱动(旧称,指同一层抽象)
lowerdir / upperdir lower directory / upper directory 只读下层 / 可写上层
copy-up copy-up 写时复制:改下层文件时先复制到可写层
union mount union mount 联合挂载
drop-in drop-in unit file 覆盖片段(systemd 的局部配置)
LD_PRELOAD --- 动态链接器的预加载机制(被代理工具用来劫持连接)

排存储问题只有一条路径:docker info 确认驱动 → repositories.json 拿到镜像 ID → imagedb 拿层 ID → layerdb 拿 cache-id → overlay2/<cache-id>/diff 看文件。四跳走通,所有「镜像在哪」的问题都有答案。而配置类问题更简单:daemon.json 管拉取与监听,systemd 的 ExecStart 管命令行参数,两者冲突时以失败启动告终 ------先清 ExecStart,再写 hosts,最后 daemon-reload 加 restart,顺序不能反。

相关推荐
坤坤子吖6 小时前
Linux网络编程:自定义应用层协议与 JSON 序列化
linux·服务器·网络·c++·笔记·json
方便面不加香菜7 小时前
Docker Volume -- 管理卷Volume详解
运维·docker·容器
江湖有缘8 小时前
Docker实战 | 使用Docker部署lemonitor镜像站点列表程序
运维·docker·容器
莫晓燚8 小时前
mall 电商项目完整部署---实战
linux·运维·docker·容器
code2cat18 小时前
【随笔】结构化输出:JSON格式过关,业务数据就能入库吗
json·大语言模型
我不会起名字3221 天前
K8s Pod 反复重启 CrashLoopBackOff:从 exit code 到根因的 5 步排查
云原生·容器·kubernetes·k8s
瑞克-桑切斯1 天前
ERP 企业生产管理系统部署手册
运维·docker·云原生·容器·eureka
瑞克-桑切斯1 天前
docker mail商城
运维·docker·容器
java_logo1 天前
Docker 部署 ReMe:轻松搭建 AI Agent 记忆管理平台
人工智能·docker·容器·ai agent·轩辕镜像·reme·agentscope-ai