挂载绑定

当您使用绑定挂载时,主机上的文件或目录会从主机挂载到容器中。相比之下,当您使用卷时,会在主机上的 Docker 存储目录中创建一个新目录,并由 Docker 管理该目录的内容。

何时使用绑定挂载

绑定挂载适用于以下类型的使用场景:

  • 在 Docker 主机上的开发环境和容器之间共享源代码或构建产物。

  • 当您想要在容器中创建或生成文件并将这些文件持久化到主机文件系统上时。

  • 将主机上的配置文件共享到容器。这就是 Docker 默认为容器提供 DNS 解析的方式,通过将主机上的 /etc/resolv.conf 挂载到每个容器中。

挂载卷也可用于构建:您可以将主机上的源代码挂载到构建容器中,以测试、检查或编译项目。

挂载到现有数据上

如果您将文件或目录挂载到容器中已存在文件或目录的目录中,预先存在的文件会被挂载所遮挡。这类似于您在 Linux 主机上保存文件到 /mnt,然后将 USB 驱动器挂载到 /mnt/mnt 的内容会被 USB 驱动器的内容遮挡,直到卸载 USB 驱动器。

使用容器时,没有直接的方法来移除挂载以重新显示被隐藏的文件。最好的选择是重新创建不包含该挂载的容器。

注意事项和限制条件

  • 绑定挂载默认对主机上的文件具有写入权限。

    使用绑定挂载的一个副作用是,你可以通过在容器中运行的进程来更改主机文件系统,包括创建、修改或删除重要的系统文件或目录。这种能力可能会带来安全风险。例如,它可能会影响主机系统上非Docker进程。

    您可以使用 readonlyro 选项来阻止容器写入挂载点。

  • 挂载卷是创建在 Docker 守护进程主机上的,而不是客户端。

    如果您正在使用远程 Docker 守护进程,则无法创建绑定挂载来访问容器中客户端计算机上的文件。

    对于 Docker Desktop,守护进程运行在 Linux 虚拟机中,而不是直接在本地主机上运行。Docker Desktop 内置了透明处理绑定挂载的机制,允许您将本地主机文件系统路径与虚拟机中运行的容器共享。

  • 使用绑定挂载的容器与主机紧密相关。

    绑定挂载依赖于主机文件系统具有特定的目录结构。这种依赖性意味着,如果在没有相同目录结构的不同主机上运行,带有绑定挂载的容器可能会失败。

语法

要创建绑定挂载,你可以使用 --mount--volume 标志。

$ docker run --mount type=bind,src=<host-path>,dst=<container-path>
$ docker run --volume <host-path>:<container-path>

通常情况下,首选 --mount。主要区别在于 --mount 标志更明确,并支持所有可用选项。

如果您使用 --volume 来绑定挂载一个在 Docker 主机上尚不存在的文件或目录,Docker 会自动在主机上为您创建该目录。该目录总是被创建为目录形式。

--mount 如果指定的挂载路径在主机上不存在,则不会自动创建目录。相反,它会报错:

$ docker run --mount type=bind,src=/dev/noexist,dst=/mnt/foo alpine
docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /dev/noexist.

--mount 的选项

--mount 标志由多个键值对组成,用逗号分隔,每个键值对由一个 <key>=<value> 元组组成。键的顺序不重要。

$ docker run --mount type=bind,src=<host-path>,dst=<container-path>[,<key>=<value>...]

--mount type=bind 的有效选项包括:

选项描述
source, src主机上文件或目录的位置。这可以是绝对路径或相对路径。
destination, dst, target文件或目录在容器中挂载的路径。必须为绝对路径。
readonly, ro如果存在,会导致绑定挂载 以只读方式挂载到容器中
bind-propagation如果存在,更改 绑定传播
示例
$ docker run --mount type=bind,src=.,dst=/project,ro,bind-propagation=rshared

--volume 的选项

--volume-v 标志由三个字段组成,用冒号字符(:)分隔。字段必须按正确的顺序排列。

$ docker run -v <host-path>:<container-path>[:opts]

第一个字段是主机上要绑定挂载到容器中的路径。第二个字段是文件或目录在容器中挂载的路径。

第三个字段是可选的,是一个用逗号分隔的选项列表。对于带有绑定挂载的 --volume,有效选项包括:

选项描述
readonly, ro如果存在,会导致绑定挂载 以只读方式挂载到容器中
z, Z配置 SELinux 标签。请参阅 配置 SELinux 标签
rprivate (默认)将此挂载的绑定传播设置为 rprivate。请参阅 配置绑定传播
private将此挂载的绑定传播设置为 private。请参阅 配置绑定传播
rshared将此挂载的绑定传播设置为 rshared。请参阅 配置绑定传播
shared将此挂载的绑定传播设置为 shared。请参阅 配置绑定传播
rslave将此挂载的绑定传播设置为 rslave。请参阅 配置绑定传播
slave将此挂载的绑定传播设置为 slave。请参阅 配置绑定传播
示例
$ docker run -v .:/project:ro,rshared

使用绑定挂载启动容器

考虑一种情况,你有一个目录 source,并且在构建源代码时,构建产物被保存到另一个目录 source/target/。 你希望这些构建产物在容器中可用,路径为 /app/,并且希望每次在开发主机上构建源代码时,容器都能访问新的构建产物。 使用以下命令将 target/ 目录绑定挂载到容器中的 /app/。 在 source 目录中运行该命令。$(pwd) 子命令在 Linux 或 macOS 主机上扩展为当前工作目录。 如果你使用的是 Windows,请参阅 Windows 上的路径转换

以下 --mount-v 示例产生相同的结果。除非在运行第一个示例后移除 devtest 容器,否则您无法同时运行它们。


$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app \
  nginx:latest
$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  nginx:latest

使用 docker inspect devtest 验证绑定挂载是否创建 正确。查找 Mounts 部分:

"Mounts": [
    {
        "Type": "bind",
        "Source": "/tmp/source/target",
        "Destination": "/app",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
    }
],

这表明挂载是一个 bind 挂载,它显示了正确的源和目标,它显示挂载是读写模式,并且传播设置为 rprivate

停止并移除容器:

$ docker container rm -fv devtest

挂载到容器中的非空目录

如果您将一个目录挂载到容器中一个非空目录,该目录的现有内容会被挂载隐藏。这可能是有益的,例如当您想在不构建新镜像的情况下测试应用程序的新版本时。然而,这也可能令人意外,且这种行为与 volumes 不同。

这个例子被设计得极端,但是会用宿主机上的 /tmp/ 目录替换容器中的 /usr/ 目录内容。 在大多数情况下,这会导致容器无法正常运行。

--mount-v 示例具有相同的最终结果。


$ docker run -d \
  -it \
  --name broken-container \
  --mount type=bind,source=/tmp,target=/usr \
  nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262:
starting container process caused "exec: \"nginx\": executable file not found in $PATH".
$ docker run -d \
  -it \
  --name broken-container \
  -v /tmp:/usr \
  nginx:latest

docker: Error response from daemon: oci runtime error: container_linux.go:262:
starting container process caused "exec: \"nginx\": executable file not found in $PATH".

容器已创建但未启动。将其删除:

$ docker container rm broken-container

使用只读绑定挂载

对于某些开发应用程序,容器需要 写入挂载点,以便更改传播回 Docker 主机。在其他时候,容器只需要读取访问权限。

此示例修改了前一个示例,但通过在容器内的挂载点之后将 ro 添加到(默认为空的)选项列表中,将目录挂载为只读绑定挂载。当存在多个选项时,用逗号分隔它们。

--mount-v 示例的结果相同。


$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app,readonly \
  nginx:latest
$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app:ro \
  nginx:latest

使用 docker inspect devtest 验证绑定挂载是否创建 正确。查找 Mounts 部分:

"Mounts": [
    {
        "Type": "bind",
        "Source": "/tmp/source/target",
        "Destination": "/app",
        "Mode": "ro",
        "RW": false,
        "Propagation": "rprivate"
    }
],

停止并移除容器:

$ docker container rm -fv devtest

递归挂载

当您挂载一个本身包含挂载的路径时,这些子挂载默认也会包含在绑定挂载中。此行为是可配置的,通过使用 bind-recursive 选项来实现。此选项仅支持 --mount 标志,不支持 -v--volume

如果绑定挂载是只读的,Docker 引擎会尽力将子挂载也设置为只读。这被称为递归只读挂载。递归只读挂载需要 Linux 内核版本 5.12 或更高版本。如果您运行的是较旧的内核版本,子挂载默认会自动以读写方式挂载。在早于 5.12 的内核版本上尝试使用 bind-recursive=readonly 选项将子挂载设置为只读会导致错误。

bind-recursive 选项支持的值有:

描述
enabled (默认)如果内核版本为 v5.12 或更高版本,只读挂载将递归设置为只读。否则,子挂载为读写模式。
disabled子挂载会被忽略(不包含在绑定挂载中)。
writable子挂载是可读写的。
readonly子挂载是只读的。需要内核 v5.12 或更高版本。

配置绑定传播

对于绑定挂载和卷,绑定传播默认为 rprivate。它仅可配置绑定挂载,且仅限于 Linux 主机。绑定传播是一个高级主题,许多用户无需配置它。

绑定传播是指在一个给定的绑定挂载中创建的挂载是否可以传播到该挂载的副本。考虑一个挂载点 /mnt,它也被挂载在 /tmp 上。传播设置控制 /tmp/a 上的挂载是否也在 /mnt/a 上可用。每个传播设置都有一个递归的对应项。在递归的情况下,假设 /tmp/a 也被挂载为 /foo。传播设置控制 /mnt/a 和/或 /tmp/a 是否存在。

注意

挂载传播在 Docker Desktop 上无法工作。

传播设置描述
shared原始挂载的子挂载暴露给副本挂载,副本挂载的子挂载也会传播到原始挂载。
slave类似于共享挂载,但是仅在一个方向上。如果原始挂载暴露了一个子挂载,副本挂载可以看到它。然而,如果副本挂载暴露了一个子挂载,原始挂载无法看到它。
private该挂载是私有的。其中的子挂载不会暴露给副本挂载,副本挂载的子挂载也不会暴露给原始挂载。
rshared与共享相同,但传播也会扩展到原始或副本挂载点中嵌套的挂载点。
rslave与 slave 相同,但传播也会扩展到原始或副本挂载点中嵌套的挂载点。
rprivate默认值。与 private 相同,意味着在原始或副本挂载点内的任何位置都没有挂载点在任一方向上进行传播。

在设置挂载点的绑定传播之前,主机文件系统需要已经支持绑定传播。

有关绑定传播的更多信息,请参阅 Linux 内核文档中的共享子树

以下示例将 target/ 目录两次挂载到容器中, 第二次挂载同时设置了 ro 选项和 rslave 绑定传播选项。

--mount-v 示例的结果相同。


$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app \
  --mount type=bind,source="$(pwd)"/target,target=/app2,readonly,bind-propagation=rslave \
  nginx:latest
$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app \
  -v "$(pwd)"/target:/app2:ro,rslave \
  nginx:latest

现在如果你创建 /app/foo//app2/foo/ 也存在。

配置 SELinux 标签

如果您使用 SELinux,可以添加 zZ 选项来修改挂载到容器中的主机文件或目录的 SELinux 标签。这会影响主机本身上的文件或目录,并可能在 Docker 范围之外产生后果。

  • z 选项表示绑定挂载内容在多个容器之间共享。
  • The Z 选项表示绑定挂载内容是私有的且不共享。

使用这些选项时要格外小心。使用 Z 选项将系统目录(如 /home/usr)绑定挂载会导致主机无法操作,您可能需要手动重新标记主机文件。

重要

在使用服务时使用绑定挂载,SELinux 标签 (:Z:z),以及 :ro 会被忽略。详情请参阅 moby/moby #32579

此示例设置 z 选项以指定多个容器可以共享 绑定挂载的内容:

无法使用 --mount 标志修改 SELinux 标签。

$ docker run -d \
  -it \
  --name devtest \
  -v "$(pwd)"/target:/app:z \
  nginx:latest

在 Docker Compose 中使用绑定挂载

单个带有绑定挂载的 Docker Compose 服务如下所示:

services:
  frontend:
    image: node:lts
    volumes:
      - type: bind
        source: ./static
        target: /opt/app/static
volumes:
  myapp:

有关在 Compose 中使用 bind 类型的卷的更多信息,请参阅 Compose 卷参考文档。 以及 Compose 卷配置参考文档

后续步骤