引言
在使用 Docker Desktop 运行容器时,开发者经常会遇到各种复杂的文件系统路径。特别是当我们通过 -v 参数挂载本地目录时,Docker 会创建一系列复杂的存储结构。
本文将通过一个具体的 LocalAI 容器案例,深入解析 Docker Desktop 中两个不同存储路径的区别和原理。

先说结论
简单说,这两个路径是Docker在后台"干活"留下的不同痕迹。
第一个是Containerd运行时管理的,相当于容器的"原始底稿",就是你挂载卷之前容器里自带的/build目录内容。第二个是Docker自己管理的,相当于运行时的"工作现场",包含了卷挂载后的状态。
但其实这两个路径你都不用管,因为你的数据实际上在Windows的D:/localai-data/里。容器里的/build只是映射过来的窗口,你在Windows那边改了文件,容器里就能看到。
所以记住:你的模型和配置都在D盘那个文件夹,Docker内部那些复杂路径不用操心。
案例背景
我运行了以下命令启动一个 LocalAI 容器:
bash
docker run -d --name local-ai --gpus all -p 8080:8080 \
-v D:/localai-data/models:/build/models \
-v D:/localai-data/config:/build/config \
localai/localai:latest-gpu-nvidia-cuda-12
然后发现在 WSL 中有两个看起来相似但不同的路径:
-
Containerd 快照路径:
\\wsl.localhost\docker-desktop\mnt\docker-desktop-disk\data\desktop-containerd\daemon\io.containerd.snapshotter.v1.overlayfs\snapshots\284\fs\build -
Docker OverlayFS 路径:
\\wsl.localhost\docker-desktop\mnt\docker-desktop-disk\data\docker\rootfs\overlayfs\f0e048f2205532a006623a8e02fef1534a391646e84047a51ffb5f560a616967\build
技术架构深度解析
1. Docker Desktop 的存储架构
Docker Desktop 在 Windows 上使用 WSL 2 运行时,采用了多层存储架构:
Windows 主机
├── WSL 2
│ ├── Docker Desktop VM
│ │ ├── Containerd 运行时
│ │ └── Docker 守护进程
│ └── 用户 WSL 发行版
└── Windows 文件系统
2. Containerd 快照路径解析
路径:
\\wsl.localhost\docker-desktop\mnt\docker-desktop-disk\data\desktop-containerd\daemon\io.containerd.snapshotter.v1.overlayfs\snapshots\284\fs\build
这是 Containerd 的 OverlayFS 快照层
结构分解:
desktop-containerd/daemon/- Containerd 守护进程数据目录io.containerd.snapshotter.v1.overlayfs/- OverlayFS 快照管理器snapshots/284/- 第 284 号快照(随机ID)fs/- 容器的根文件系统build/- 容器内的 /build 目录
特点:
- Containerd 管理:由 Containerd 容器运行时直接管理
- 快照机制:使用 OverlayFS 的快照(snapshot)功能
- 原始容器视图:展示容器挂载卷之前的原始文件系统状态
- 可写层基础:是容器可写层(upperdir)的基础
验证命令:
bash
# 查看容器在 Containerd 中的信息
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
redcoolbeans/dockercontainerspy containerd-list
# 在容器内查看 OverlayFS 挂载
docker exec local-ai mount | grep overlay
3. Docker OverlayFS 路径解析
路径:
\\wsl.localhost\docker-desktop\mnt\docker-desktop-disk\data\docker\rootfs\overlayfs\f0e048f2205532a006623a8e02fef1534a391646e84047a51ffb5f560a616967\build
这是 Docker 的 OverlayFS 存储驱动层
结构分解:
docker/rootfs/overlayfs/- Docker 的 OverlayFS 存储驱动目录f0e048f220...- 64 位随机哈希,标识特定容器层build/- 容器内的 /build 目录
特点:
- Docker 管理:由 Docker 守护进程管理
- 存储驱动:使用 Docker 的 OverlayFS 存储驱动
- 联合挂载:可能包含多个镜像层的联合视图
- 运行时状态:包含卷挂载后的文件系统状态
验证命令:
bash
# 查看容器的存储驱动信息
docker inspect local-ai --format='{{.GraphDriver}}'
# 查看具体的存储驱动数据
docker inspect local-ai --format='{{json .GraphDriver.Data}}'
关键区别对比
| 特性 | Containerd 快照路径 | Docker OverlayFS 路径 |
|---|---|---|
| 管理者 | Containerd 运行时 | Docker 守护进程 |
| 用途 | 容器基础镜像快照 | 容器运行时的存储层 |
| 数据内容 | 挂载卷前的原始数据 | 可能包含运行时修改 |
| 持久性 | 基础层,通常只读 | 包含可写层 |
| 生命周期 | 与镜像层关联 | 与容器实例关联 |
实际操作验证
1. 查看容器存储详情
bash
# 获取容器 ID
docker ps -qf "name=local-ai"
# 查看容器详情
docker inspect local-ai | grep -A 10 -B 5 "GraphDriver"
# 输出示例:
# "GraphDriver": {
# "Data": {
# "LowerDir": "/var/lib/docker/overlay2/xxx/diff:/var/lib/docker/overlay2/yyy/diff",
# "MergedDir": "/var/lib/docker/overlay2/zzz/merged",
# "UpperDir": "/var/lib/docker/overlay2/zzz/diff",
# "WorkDir": "/var/lib/docker/overlay2/zzz/work"
# },
# "Name": "overlay2"
# }
2. 在容器内验证挂载
bash
# 进入容器
docker exec -it local-ai sh
# 查看 /build 目录结构
ls -la /build/
# 应该看到 models 和 config 目录
# 查看挂载信息
mount | grep /build
# 输出应显示从 Windows 主机挂载的目录
3. 查看卷映射
bash
# 查看容器的卷挂载
docker inspect local-ai --format='{{json .Mounts}}'
# 输出应显示:
# [
# {
# "Type": "bind",
# "Source": "/mnt/d/localai-data/models",
# "Destination": "/build/models",
# "Mode": "",
# "RW": true,
# "Propagation": "rprivate"
# },
# {
# "Type": "bind",
# "Source": "/mnt/d/localai-data/config",
# "Destination": "/build/config",
# "Mode": "",
# "RW": true,
# "Propagation": "rprivate"
# }
# ]
为什么有两个相似的 /build 目录?
1. 挂载覆盖机制
当使用 -v 参数挂载卷时,Docker 使用了 Linux 的挂载覆盖机制:
容器 /build 原始目录
卷挂载点
Windows D:/localai-data/models
Windows D:/localai-data/config
容器运行时视图 /build
2. 两个路径的实际关系
-
Containerd 路径:
- 包含容器镜像中原始的
/build目录内容 - 在卷挂载前存在
- 卷挂载后,这个目录被隐藏(masked)
- 包含容器镜像中原始的
-
Docker OverlayFS 路径:
- 是运行时视图的一部分
- 可能包含容器修改的元数据
- 实际的卷挂载在这个视图之上
3. 验证实验
bash
# 1. 停止容器
docker stop local-ai
# 2. 查看 Containerd 路径
# 此时应该能看到原始的 /build 目录内容
# 3. 启动容器
docker start local-ai
# 4. 再次查看 Containerd 路径
# 由于卷已挂载,原始 /build 目录被隐藏
实际影响和注意事项
开发者需要知道的
-
数据存储位置:
- 你的模型文件实际存储在
D:/localai-data/models/ - Docker 容器内的路径只是映射视图
- 你的模型文件实际存储在
-
性能考虑:
- Windows → WSL → Docker 的三层转换有一定性能开销
- 大量小文件操作时尤其明显
-
备份策略:
- 备份
D:/localai-data/目录即可 - 不需要备份 Docker 内部的路径
- 备份
-
权限问题:
- Windows 和 Linux 文件权限系统不同
- 可能需要配置 WSL 的权限映射
优化建议
-
使用 Docker 卷替代绑定挂载:
bashdocker volume create localai-models docker volume create localai-config docker run -d --name local-ai \ -v localai-models:/build/models \ -v localai-config:/build/config \ localai/localai:latest-gpu-nvidia-cuda-12 -
性能优化配置:
json// 在 Docker Desktop 设置中添加 { "wslEngineEnabled": true, "wslEngineOptimization": "performance" } -
监控存储使用:
bash# 查看容器存储使用 docker system df # 查看详细存储信息 docker system df -v
高级调试技巧
1. 查看 OverlayFS 层次结构
bash
# 在 WSL 中
wsl -d docker-desktop
# 查找容器相关层
find /var/lib/docker/overlay2 -name "lower" -type f | xargs grep -l "local-ai"
# 查看层关联
cat /var/lib/docker/image/overlay2/layerdb/mounts/*/mount-id
2. 使用 dive 工具可视化
bash
# 安装 dive
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest localai/localai:latest-gpu-nvidia-cuda-12
3. 文件系统事件监控
bash
# 在容器运行时监控文件访问
docker run --rm -it \
--pid=container:local-ai \
--cap-add SYS_PTRACE \
alpine sh -c "apk add strace && strace -p 1 -e trace=file"
常见问题解答
Q1: 为什么我的文件修改在容器内看不到?
A: 检查 Windows 到 WSL 的文件系统同步,可能需要重启 Docker Desktop 或 WSL。
Q2: 如何清理这些临时文件?
A: 使用 docker system prune -a 清理,但注意这会删除所有未使用的资源。
Q3: 这两个目录哪个是"真实"的?
A: 都不是"真实"的数据存储位置。你的真实数据在 Windows 的 D:/localai-data/ 目录中。
Q4: 如何提高文件访问性能?
A: 考虑将数据放在 Linux 文件系统中,或使用 Docker 卷而非绑定挂载。
总结
通过深入分析这两个路径,我们可以理解 Docker Desktop 的复杂存储架构:
- 分层管理:Docker 使用多层存储(镜像层、容器层、挂载层)
- 运行时抽象:Containerd 和 Docker 守护进程协同工作
- 挂载覆盖:绑定挂载会覆盖容器内的原始目录
- 性能优化:理解架构有助于优化存储性能
对于大多数开发者,关键要点是:
- 理解你的数据实际存储位置
- 使用适当的挂载方式
- 定期清理不再使用的存储资源
- 监控存储使用情况
掌握这些底层知识,可以帮助你更有效地使用 Docker,并能在出现问题时快速定位和解决存储相关问题。