Docker + WSL 挂载踩坑记:同一条命令,为什么挂到了两套文件系统

背景

博客后端是 Spring Boot 加 PostgreSQL 加 nginx 的一套 docker compose 应用,数据文件一直放在 WSL2 里 Ubuntu-22.04 的 /volume/Blog 下,容器通过 bind mount 直接引用:

volumes:
  - /volume/Blog/postgres/data:/var/lib/postgresql/data
  - /volume/Blog/nginx/nginx.conf:/etc/nginx/nginx.conf

这套配置沿用已久,期间反复部署过很多次,从来没出过问题。/volume 这个前缀在人的直觉里就是"这台机器上的目录",既然 Ubuntu 里能看到这些文件,挂载自然也是挂的它们。这个默认假设后来成了整个排查里最深的坑。

现象:三个容器同时故障,报错各不相同

某次在 Windows PowerShell 里执行 docker compose up -d 之后,三个容器同时异常,而且各有各的报法:

容器 表现
bg-nginx 启动失败:error mounting /volume/Blog/nginx/nginx.conf ... not a directory
bg-postgres 挂载后做了全新初始化,blog 数据库不存在
bg-api 启动失败:FATAL: database "blog" does not exist

单看任何一条,都像是某个服务自己的配置问题。放在一起看,嫌疑就集中到挂载层了——如果挂载源搞错了,这三个症状都能自洽。

两个最顺手的解释,都不成立

第一反应是权限。bind mount 对宿主机目录可读性的失败,表现是 Permission denied,而且在容器创建或启动阶段就直接报错;而这次挂载是成功的,只是内容不对。not a directory 更说明源路径确实存在,只不过它是个目录——一个不知道什么时候被自动创建出来的空目录。权限解释不了这个现象。

第二反应是数据丢了。去 WSL 里查 /volume/Blog:nginx.conf 是 1149 字节的真实文件,postgres 数据目录是几个月前初始化的旧集群(PG_VERSION=16,base/ 下躺着多个业务库目录),数据一个字节没少。矛盾就出现了:文件明明在 Ubuntu 里,容器却看不到;数据目录明明非空,postgres 却从头初始化了一遍。排除掉权限和数据丢失之后,剩下的唯一解释是——挂载源被解析到了另一套文件系统。

对照实验:同一份挂载参数,两端两个结果

与其继续推测,不如做个受控实验。用完全相同的挂载参数,分别在 WSL shell 和 Windows PowerShell 里启动两个 nginx 容器,目标目录里预先放好带标记内容的文件:

-v /home/xxx/nginx-mount-wsl:/usr/share/nginx/html:ro
执行环境 页面响应 容器内 ls 看到的目录
WSL shell 内运行 docker 返回 WSL 目录里的标记文件 真实文件(index.html 等)
Windows PowerShell 直接运行 403 Forbidden 空目录

docker inspect 显示两个容器的挂载 Source 字符串完全一致,实际指向的文件系统却完全不同。实验把结论钉死了:这不取决于配置,只取决于命令从哪一端进来。

原理:路径归属由 docker 客户端上下文决定

Docker Desktop 的 WSL2 后端里,引擎跑在 docker-desktop 虚拟机中,docker 只是客户端。bind mount 的绝对路径归属由客户端上下文决定:命令在 WSL 里执行时,路径被识别为 WSL 文件系统路径,桥接回 WSL 发行版的真实目录;命令在 Windows 里执行时,路径落到引擎虚拟机自己的根文件系统,目录不存在就先自动创建一个空的。

同一条命令,在两端产生了两个世界。一个是积累了数月数据的真实目录,另一个是每次启动前临时生成、看起来"缺文件"的空目录。postgres 挂到空目录,于是初始化出全新集群;nginx.conf 挂到自动创建的目录,于是报 not a directory;bg-api 连库时发现 blog 不存在。三个互不相关的故障,是同一个根因在不同容器上的三种显影。

这个机制一直存在,不是这次才引入的行为,只是之前恰好都在能解析到正确路径的上下文里部署,从未暴露。

结论

不是权限问题,也不是数据丢失,而是 bind mount 的路径解析跟着 docker 客户端上下文变。容器自始至终没有挂载过 WSL 的真实数据目录,数据从未离开原位置;所谓"blog 数据库消失""nginx.conf 变目录",都是容器在另一套文件系统上的所见。要挂载 WSL 内目录,docker 命令必须在 WSL shell 里执行。排查这类问题时,与其在权限和配置里打转,不如先确认挂载源到底指向哪套文件系统——一次最小挂载实验就能定位。

实践建议

部署动作(build、compose up)统一在 WSL 里执行,和数据目录所在文件系统保持一致,这是成本最低也最不容易错的做法。如果必须在 Windows 侧操作,优先改用 Docker 命名卷,把路径归属问题彻底绕开;挂载 Windows 目录时用 Windows 风格路径,让它落在工程范围内的文件系统上。从 Windows 挂 WSL 目录并非不可能,\\wsl$\Ubuntu-22.04\... 形式的 UNC 路径可以工作,但性能低于原生挂载,只适合低频场景。

日常留一个判断手段:docker run --rm -v <绝对路径>:/tmp/x:ro <镜像> ls /tmp/x。容器里看到的内容和预期一致,说明挂载方向正确;看到空目录或类型不符,就先怀疑解析上下文,而不是第一时间动权限或者恐慌数据丢失。

posted @ 2026-10-10 12:13  PC2005-cloud  阅读(17)  评论(0)    收藏  举报