Docker 基础体系 · 第 45/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。

Docker Bind Mount 深入:传播、递归、只读、符号链接和平台差异

Bind mount(绑定挂载)不是 Docker 自己实现的一种文件复制机制,而是把宿主机上的一个路径挂载到容器的 mount namespace 中。容器看到的是同一组底层文件或目录,只是通过另一个挂载点访问。

最基本的命令是:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app \
  alpine \
  ls -la /app

这里有三个不同层次的对象:

  1. /srv/app:Docker daemon 所在 Linux 主机上的源路径;
  2. /app:容器 mount namespace 中的目标路径;
  3. bind mount:把源路径对应的文件系统对象接入目标路径的挂载关系。

它不是“把 /srv/app 的内容复制到 /app”。容器启动后,宿主机对 /srv/app 的修改通常可以立即被容器看到,容器对可写 bind mount 的修改也会直接写入宿主机。


一、先建立 Linux 挂载模型

Linux 进程访问路径时,并不是简单地从字符串查找文件。路径解析会经过当前进程所在的 mount namespace,并在遇到挂载点时切换到相应的文件系统。

例如:

宿主机路径树:

/srv/app/
├── main.py
└── data/
    └── db.sqlite

bind mount:

/srv/app  ───────────────►  容器 /app

容器看到:

/app/
├── main.py
└── data/
    └── db.sqlite

容器中的 /app/main.py 与宿主机的 /srv/app/main.py 指向同一个底层对象;但容器中的 /app 位于容器自己的 mount namespace 中,因此“哪些挂载点可见”和“后续挂载事件是否传播”仍然可以独立控制。

Bind mount 与 Volume 的关键区别

Bind mount 的源是用户指定的宿主机路径:

--mount type=bind,src=/srv/app,dst=/app

Volume 的源则由 Docker 管理:

--mount type=volume,src=app-data,dst=/app

Volume 通常位于 Docker 的数据目录中,由 Docker 创建和管理;bind mount 直接暴露宿主机路径,因此更适合源码、配置、设备节点或需要与宿主机共享的目录,但也更容易受到宿主机权限、路径结构和安全策略影响。


二、--mount-v:语义相近,错误行为不同

同一个 bind mount 可以写成:

docker run --rm \
  -v /srv/app:/app:ro \
  alpine

或者:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app,readonly \
  alpine

现代脚本更适合使用 --mount,因为参数是显式的:

  • type=bind 明确指定挂载类型;
  • srcdst 不容易因为冒号、路径格式而产生歧义;
  • readonlybind-propagationbind-recursive 等选项可以直接表达。

两者还有一个经常导致故障的差异:源路径不存在时,-v 通常会在宿主机上创建一个目录,而 --mount 默认会报错。

docker run --rm \
  -v /does/not/exist:/app \
  alpine \
  ls -ld /app

这可能得到一个新建的空目录,导致应用“启动成功但读不到配置”。

而:

docker run --rm \
  --mount type=bind,src=/does/not/exist,dst=/app \
  alpine

通常直接失败,因为源路径不存在。对于生产部署,后者更容易尽早发现路径拼写错误。

-v 的短语法也更容易受到平台路径格式影响,例如 Windows 路径中的盘符冒号。跨平台 Compose 配置通常应优先使用长语法。


三、递归:初始绑定时是否包含子挂载点

“递归”在 bind mount 中有两个相关但不同的含义:

  1. 初始绑定是否包含源目录下已经存在的子挂载点
  2. 后续在目录内部创建的挂载事件是否传播

这两个行为不能混为一谈。

假设宿主机上存在:

/srv/app/                  普通目录
/srv/app/cache/            另一个挂载点,例如 tmpfs

可以抽象为:

/srv/app
└── cache/  ← 独立挂载

如果执行:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app,bind-recursive=enabled \
  alpine

容器中的 /app/cache 会继续呈现为那个子挂载点。enabled 表示递归包含已有子挂载;这是现代 Docker 在 Linux 上常见的默认行为。

如果使用:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app,bind-recursive=disabled \
  alpine

则只绑定 /srv/app 本身,不把 /srv/app/cache 这个子挂载带入容器。容器看到的 /app/cache 将是父文件系统中被子挂载覆盖之前的普通目录内容。

因此,“绑定了 /srv/app”并不必然等价于“容器可以看到这个目录树当前的所有文件系统”。

递归选项的常见取值

现代 Docker Engine 的 --mount bind 选项支持的语义包括:

  • enabled:包含子挂载;
  • disabled:不包含子挂载;
  • writable:递归包含,并允许子挂载保持可写;
  • readonly:递归包含,并尝试把子挂载也设为只读。

readonly 递归只读依赖 Linux 内核对递归只读 bind mount 的支持,通常要求 Linux 5.12 或更高版本。Docker Engine、内核和底层文件系统不满足条件时,命令可能失败,不能把它当成所有 Linux 环境都可用的能力。

例如:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app,readonly,bind-recursive=readonly \
  alpine \
  sh -c 'touch /app/cache/test'

在支持该能力且容器以有足够权限的用户运行时,预期会得到类似:

touch: /app/cache/test: Read-only file system

这里要区分“权限不足”和“文件系统只读”:

  • Permission denied 可能只是 UID/GID 或 DAC 权限不匹配;
  • Read-only file system 才说明挂载层面的只读标志阻止了写入。

一个可观察的递归实验

以下实验需要 Linux 主机上的 root 权限,并且 Docker daemon 能看到这些主机路径:

sudo mkdir -p /tmp/bm-demo/sub
sudo mount -t tmpfs tmpfs /tmp/bm-demo/sub

现在 /tmp/bm-demo/sub 是一个 tmpfs 子挂载。分别运行:

docker run --rm \
  --mount type=bind,src=/tmp/bm-demo,dst=/mnt,bind-recursive=enabled \
  alpine \
  cat /proc/self/mountinfo

在输出中搜索 /mnt/sub,应能看到对应的挂载记录。

再运行:

docker run --rm \
  --mount type=bind,src=/tmp/bm-demo,dst=/mnt,bind-recursive=disabled \
  alpine \
  sh -c 'grep " /mnt/sub " /proc/self/mountinfo || true'

通常不会看到 /mnt/sub 作为独立挂载点。此时容器仍可能看到一个名为 sub 的目录,但它不再是宿主机上的那个 tmpfs 挂载。

实验结束后:

sudo umount /tmp/bm-demo/sub
sudo rmdir /tmp/bm-demo/sub /tmp/bm-demo

这个实验说明:递归控制的是“挂载树”,不是目录项复制。


四、传播:挂载事件是否跨 namespace 传递

挂载传播(mount propagation)控制的是:一个 mount namespace 中发生新的挂载、卸载或递归传播事件时,另一个 namespace 是否能看到这些变化。

它传播的是挂载事件,不是普通文件写入。

例如,容器向 /mnt 中写入一个文件:

容器 /mnt/x

只要 bind mount 可写,宿主机文件内容通常就会立即变化。这是普通文件共享,与 mount propagation 无关。

但如果容器内部执行:

mount -t tmpfs tmpfs /mnt/runtime

这会创建一个新的挂载点。这个新挂载点是否能出现在宿主机对应目录下,才是 propagation 的问题。

四种主要传播模式

模式 容器看到宿主机后续挂载 宿主机看到容器后续挂载
private
rprivate
slave 是,单向
rslave 是,递归单向
shared
rshared 是,递归

前缀 r 表示递归应用到挂载点下面的挂载树。例如:

  • shared 只描述当前挂载;
  • rshared 还递归处理其下的子挂载。

Docker Linux bind mount 的默认传播通常是 rprivate。这意味着普通 bind mount 中:

  • 宿主机后来新建的子挂载不会自动出现在容器中;
  • 容器后来创建的挂载也不会自动出现在宿主机中。

可以显式指定:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app,bind-propagation=rslave \
  alpine

或者:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app,bind-propagation=rshared \
  alpine

为什么 rshared 可能失败

传播不是单方面写在 Docker 参数中的标签。Linux 内核要求相关挂载处于合适的 shared/slave peer group 状态。

例如,直接要求:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app,bind-propagation=rshared \
  alpine

如果宿主机上的 /srv/app 所在挂载不允许转换为 shared,或者当前环境不支持该传播配置,Docker 可能返回类似“invalid argument”或“mount propagation not supported”的错误。

可以在 Linux 主机上检查:

findmnt -o TARGET,SOURCE,FSTYPE,PROPAGATION /srv/app

可能看到:

TARGET    SOURCE  FSTYPE  PROPAGATION
/srv/app  /dev/... ext4   private

这里的 private 只表示当前挂载的传播属性;它不是 Docker 的 readonly,也不影响普通文件读写。

传播的真实用途

典型场景是需要让容器看到宿主机后来产生的挂载点,例如:

  • 容器运行 systemd、容器运行时或编排相关组件;
  • 需要把宿主机挂载树的一部分传入容器;
  • 需要让容器创建的挂载点回到宿主机挂载树。

rshared 是双向传播,风险也最大:容器中创建的挂载事件可能出现在宿主机对应的路径下。即使应用本身不打算执行 mount,错误配置、拥有 CAP_SYS_ADMIN 的进程或特权容器都可能扩大影响范围。

如果只需要宿主机的挂载变化进入容器,而不希望容器的挂载反向传回,rslave 通常比 rshared 更符合单向输入模型。


五、传播与递归是两个正交维度

以下两个配置解决不同问题:

--mount type=bind,src=/srv/app,dst=/app,bind-recursive=disabled

解决的是:

初始绑定时是否带入 /srv/app 下面已有的子挂载?

而:

--mount type=bind,src=/srv/app,dst=/app,bind-propagation=rslave

解决的是:

绑定建立后,宿主机未来新增的挂载事件是否进入容器?

因此不能因为设置了 rslave,就认为已有子挂载一定会被递归带入;也不能因为使用了递归 bind,就认为未来的挂载事件一定会传播。

可以把状态分解成两个变量:

最终可见挂载树
    = 初始递归包含规则
    + 后续传播规则

其中:

  • 初始递归规则决定创建容器挂载时的起点;
  • 传播规则决定容器运行期间挂载树是否继续变化。

六、只读:限制写入,不等于复制快照

普通只读 bind mount:

docker run --rm \
  --mount type=bind,src=/srv/config,dst=/etc/myapp,readonly \
  alpine \
  cat /etc/myapp/config.yaml

会把目标挂载标记为只读。容器尝试修改其中的文件时,通常失败:

echo changed > /etc/myapp/config.yaml

可能得到:

sh: can't create /etc/myapp/config.yaml: Read-only file system

但只读 bind mount 有几个边界。

1. 它不改变宿主机的全局状态

只读是这个容器挂载视图中的属性。宿主机上的其他进程仍然可以修改:

容器视图:/app 只读
宿主机视图:/srv/app 仍可能可写

所以只读 bind mount 不能作为宿主机目录的全局写保护,也不能替代文件权限、ACL、SELinux 或其他主机安全控制。

2. --read-only 与 bind mount 的 readonly 不同

下面的配置:

docker run --rm \
  --read-only \
  --mount type=bind,src=/srv/config,dst=/etc/myapp,readonly \
  alpine

包含两个独立设置:

  • --read-only:把容器根文件系统设为只读;
  • readonly:把 /etc/myapp 这个 bind mount 设为只读。

只读根文件系统通常还需要显式提供可写位置,例如:

docker run --rm \
  --read-only \
  --tmpfs /tmp \
  --mount type=bind,src=/srv/config,dst=/etc/myapp,readonly \
  alpine

否则应用可能因为无法写 /tmp、PID 文件、缓存或运行时目录而启动失败。

3. 普通只读不一定递归只读

假设 /srv/app/cache 是一个独立子挂载。使用:

--mount type=bind,src=/srv/app,dst=/app,readonly

父 bind mount 是只读的,但子挂载的读写属性可能不会自动被递归改成只读。此时应根据内核和 Docker 版本使用:

--mount type=bind,src=/srv/app,dst=/app,readonly,bind-recursive=readonly

如果目标是保护整个挂载树,必须验证实际结果,而不能只检查 docker inspect 中父挂载的 RW 字段。


七、符号链接:路径字符串不是挂载边界

符号链接(symbolic link)保存的是一个路径字符串,访问它时由当前进程所在的 mount namespace 继续解析。它不是一个 bind mount,也不会自动把宿主机路径“传送”到容器中。

宿主机源路径中的符号链接

假设:

mkdir -p /srv/real
ln -s /srv/real /srv/current

使用:

docker run --rm \
  --mount type=bind,src=/srv/current,dst=/app \
  alpine

通常实际绑定的是 /srv/real 指向的目录,而不是在容器中暴露一个名为 current 的符号链接对象。源路径会在 Docker daemon 和 Linux 挂载处理过程中被解析,不能把 bind mount 当成“保留源路径字符串”的复制操作。

因此生产配置不应依赖:

/srv/current

这个符号链接是否在容器中以同样形式存在。应明确绑定最终目录,或者在启动前验证符号链接目标。

挂载树内部的符号链接

另一种情况是:

/srv/app/
└── link -> /etc

绑定 /srv/app

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app \
  alpine \
  readlink /app/link

容器中访问:

cat /app/link/passwd

/app/link 的目标是绝对路径 /etc。这个 /etc 会在容器的 mount namespace 中解析,通常对应容器自己的 /etc,而不是宿主机的 /etc

这说明一个重要事实:

bind mount 暴露的是挂载点下的对象;它不会因为目录中存在符号链接,就把符号链接指向的任意宿主机路径一起暴露出来。

相对符号链接也可能越出 /app 这个目录树:

/app/sub/up -> ../..

解析过程会回到容器 namespace 中的路径层级。它可能访问容器内的其他目录,但不会仅凭符号链接跨越到宿主机同名的未挂载路径。

符号链接的安全边界

如果容器已经获得了更大的宿主机挂载范围,例如:

-v /:/host

那么容器内当然可以通过 /host/etc 等路径访问宿主机文件。这不是符号链接绕过 bind mount,而是配置本身已经暴露了宿主机根目录。

另外,源路径验证还涉及符号链接竞态、路径替换和 daemon 权限。不要把“应用不能访问宿主机未挂载路径”误解为“任意 bind mount 都安全”。Docker daemon 通常拥有比容器进程更高的权限,源路径应由受信任的部署流程管理,不能让不可信用户任意提交宿主机路径。


八、目标路径会遮蔽容器原有内容

如果镜像中原本有:

/app/
├── default.conf
└── static/

运行时挂载:

--mount type=bind,src=/srv/app,dst=/app

容器中的 /app 会被 bind mount 覆盖。镜像原有的 default.confstatic/ 并没有被删除,但在这个容器实例中不可见。

这与文件复制不同,也因此会产生常见故障:

docker run --rm \
  --mount type=bind,src=/tmp/empty,dst=/etc/nginx \
  nginx

即使 nginx 镜像内原本存在配置文件,空宿主机目录也会把它们遮蔽掉,应用可能因此启动失败。

诊断时可以分别比较:

docker run --rm nginx ls -la /etc/nginx

和:

docker run --rm \
  --mount type=bind,src=/tmp/empty,dst=/etc/nginx \
  nginx \
  ls -la /etc/nginx

第二条命令看到的是宿主机目录内容,而不是镜像层内容。


九、Compose 中的 bind mount 表达

Compose 长语法可以明确表示 bind mount:

services:
  app:
    image: alpine:latest
    command: ["sh", "-c", "ls -la /work && sleep 3600"]
    volumes:
      - type: bind
        source: ./src
        target: /work
        read_only: true
        bind:
          propagation: rprivate

这里:

  • source: ./src 通常由 Compose 按项目目录解析;
  • target: /work 是容器内目标路径;
  • read_only: true 使该挂载在容器中只读;
  • propagation: rprivate 表示不传播后续挂载事件。

Compose 的 propagation 是 Linux mount propagation 概念。它不等价于文件同步,也不等价于 macOS Docker Desktop 中曾经出现过的 consistentcacheddelegated 等性能提示。bind-recursive 是否能通过 Compose 长语法直接表达,则取决于 Compose 实现和版本;需要递归只读等细粒度能力时,应核对当前 Compose 和 Docker Engine 的实际支持,不能仅根据 YAML 能否解析判断语义已经生效。


十、如何验证 Docker 实际建立了什么挂载

不要只根据命令行参数推测结果。容器创建后,先查看 Docker 记录:

docker inspect <container>

重点关注 Mounts

[
  {
    "Type": "bind",
    "Source": "/srv/app",
    "Destination": "/app",
    "Mode": "ro",
    "RW": false,
    "Propagation": "rprivate"
  }
]

这些字段可以确认:

  • 类型是否真的是 bind
  • Docker 最终使用的源路径;
  • 目标路径;
  • Docker 记录的读写状态;
  • propagation 状态。

但父挂载记录不一定足以说明子挂载的真实属性。Linux 容器内可查看:

docker run --rm \
  --mount type=bind,src=/srv/app,dst=/app \
  alpine \
  cat /proc/self/mountinfo

在 Linux 主机上还可以使用:

findmnt -R /srv/app
findmnt -R /path/to/container/mount

/proc/self/mountinfo 的信息比普通 mount 输出更底层,包含 mount ID、parent mount ID、挂载选项和传播 peer group 等信息。排查递归挂载与 propagation 时,应优先使用它或 findmnt

用写入测试区分不同故障

下面的测试能帮助判断失败原因:

docker run --rm \
  --user 0:0 \
  --mount type=bind,src=/srv/config,dst=/config,readonly \
  alpine \
  sh -c 'echo test > /config/write-test'

可能出现三类结果:

  1. Read-only file system:挂载只读生效;
  2. Permission denied:可能是宿主机文件权限、用户映射或安全模块阻止;
  3. 写入成功:挂载没有按预期只读,或写入的路径实际位于另一个可写子挂载中。

测试完成后不要把测试文件留在宿主机:

sudo rm -f /srv/config/write-test

十一、Linux、macOS 和 Windows 的边界

Linux:Docker daemon 使用原生 Linux mount namespace

Linux 主机上,Docker Engine daemon 通常直接调用 Linux mount API。以下能力属于这个边界内的原生机制:

  • bind mount;
  • mount namespace;
  • privateslaveshared 等传播模式;
  • 子挂载递归;
  • 递归只读;
  • /proc/self/mountinfo 观察挂载状态。

因此本文关于传播和递归的精确语义,主要针对 Linux 容器和 Linux Docker Engine。

macOS:Docker Desktop 中存在 Linux VM

macOS 上运行 Linux 容器时,Docker daemon 不在 macOS 内核中,而是在 Docker Desktop 提供的 Linux 虚拟机中。用户指定的路径需要经过 Docker Desktop 的文件共享和路径转发,最终在 Linux VM 内形成容器可见的挂载。

数据流可以简化为:

macOS 文件系统
      │
      │ Docker Desktop 文件共享层
      ▼
Linux VM 中的 daemon
      │
      │ Linux bind mount
      ▼
Linux 容器

因此:

  • src=/Users/alice/project 不是 Linux daemon 直接访问的普通 Linux 路径;
  • Linux mount propagation 不会把 macOS 内核中的挂载事件直接传播到 Linux VM;
  • 文件通知、元数据、大小写行为和大量小文件访问性能可能与原生 Linux 不同;
  • cacheddelegatedconsistent 等选项在不同 Docker Desktop 版本中的实际作用可能是性能提示或兼容性参数,不能当作 Linux propagation 使用。

如果需要验证行为,应分别测试文件可见性、写入、权限和文件通知,而不是仅在 macOS 上推断 Linux bind mount 的全部语义。

Windows:Linux 容器与 Windows 容器不是同一套模型

Windows 上的 Docker 可能运行:

  • Linux 容器:通常通过 WSL 2 或 Linux VM;
  • Windows 容器:使用 Windows 自己的容器和存储机制。

Linux 容器中的 bind mount 路径通常经过 WSL/VM 转换,Linux propagation 选项不能自然映射为 Windows 主机上的 mount namespace 事件。

Windows 容器则有自己的路径格式、卷挂载规则、权限模型和文件系统语义。Linux 示例中的:

bind-propagation=rshared
bind-recursive=readonly

不能直接视为 Windows 容器的可移植配置。

跨平台项目尤其要注意:

  • Compose 中相对路径的解析位置;
  • Windows 盘符和路径分隔符;
  • 宿主机文件的大小写敏感性;
  • UID/GID 与 Windows ACL 的差异;
  • Linux 文件通知在共享文件系统上的降级或延迟;
  • 源码目录挂载与 Dockerfile 构建上下文不是同一回事。

BuildKit 与运行时 bind mount 也不是同一种 mount

构建阶段可以写:

# syntax=docker/dockerfile:1
FROM alpine
RUN --mount=type=bind,source=.,target=/src,readonly \
    find /src -maxdepth 1 -type f -print

这里的 RUN --mount=type=bind 是 BuildKit 构建步骤中的临时挂载:

  • 它只存在于该 RUN 步骤;
  • 不会自动出现在最终镜像中;
  • 不能等同于 docker run 时的运行时 bind mount;
  • source=. 的来源是构建上下文,而不是任意 Docker daemon 主机路径。

运行时的:

docker run --mount type=bind,...

则属于容器启动后的 mount namespace 配置。两者都使用“bind mount”这个概念,但生命周期、来源路径和安全边界不同。


十二、生产中的故障路径与取舍

配置目录应该只读,但日志目录应该可写

一个应用常见的拆分是:

docker run --rm \
  --mount type=bind,src=/srv/myapp/config,dst=/etc/myapp,readonly \
  --mount type=bind,src=/srv/myapp/logs,dst=/var/log/myapp \
  myapp:latest

这样配置由宿主机提供但不能被应用覆盖,日志则可以写回宿主机。它仍然要求宿主机预先创建目录,并正确处理容器用户的 UID/GID。

不要把宿主机敏感目录作为普通工作目录暴露

下面的配置权限范围极大:

-v /:/host

它使容器能够遍历宿主机的大量文件。符号链接、只读选项和传播模式都不能把这个设计变成低风险方案:

-v /:/host:ro

只读只能阻止该容器通过这个挂载直接写入,并不能消除路径泄露、凭据读取、设备访问、内核接口暴露或其他组合攻击风险。

需要挂载事件时,先确认是否真的需要双向传播

如果容器只是需要观察宿主机下的动态挂载点:

--mount type=bind,src=/var/lib/data,dst=/data,bind-propagation=rslave

通常比:

--mount type=bind,src=/var/lib/data,dst=/data,bind-propagation=rshared

更窄。后者只有在确实需要让容器创建的挂载事件回到宿主机时才有理由使用。

备份 bind mount 要备份的是宿主机路径

Bind mount 的数据不属于 Docker Volume,因此:

docker volume ls

不会列出 /srv/myapp 这种 bind mount 的内容。备份应针对宿主机源路径,并考虑:

  • 子挂载是否被包含;
  • 是否需要暂停写入以获得一致性;
  • 文件权限、ACL、扩展属性和符号链接是否保留;
  • 数据库是否需要逻辑备份或快照。

如果用普通 tar 备份挂载树,应明确是否跨越子挂载;Linux 工具的 --one-file-system 等选项可能改变结果。备份策略必须与“递归包含哪些文件系统”保持一致,否则恢复后的目录结构可能与运行时看到的结构不同。


十三、一个完整的判断框架

遇到 bind mount 行为异常时,可以按以下因果顺序检查:

  1. 源路径是否正确
    检查路径是否存在、是否是符号链接、Docker daemon 是否能访问它。

  2. 目标路径是否遮蔽了镜像内容
    确认挂载前后的目录内容,避免把“被遮蔽”误认为“镜像没有文件”。

  3. 挂载类型是否正确
    docker inspect 确认是 bind,而不是 volume 或其他挂载。

  4. 父挂载是否只读
    查看 ModeRW,并用实际写入测试确认。

  5. 是否存在子挂载
    在宿主机和容器内用 findmnt -R/proc/self/mountinfo 检查。

  6. 递归规则是否符合预期
    区分 bind-recursive=disabledenabled 和递归只读。

  7. 传播属性是否符合运行期间需求
    rprivate 不会传播新挂载;rslave 单向接收;rshared 双向传播。

  8. 运行平台是否改变了语义
    在 Docker Desktop、WSL 2 或 Windows 容器中,不要直接套用 Linux 主机的 mount namespace 结论。

Bind mount 的核心不是“把一个目录映射到另一个目录”这么简单,而是把一个宿主机路径接入容器的挂载树。递归决定初始挂载树包含哪些子挂载,传播决定之后挂载事件如何流动,只读决定当前挂载视图是否允许写入,符号链接则按照容器 namespace 的路径规则继续解析。只有把这几个维度分开,才能准确解释“文件能不能看到”“文件能不能写”“新挂载会不会出现”以及“某个平台上的行为为什么不同”。


系列导航与关联阅读

官方资料

本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。