绑定挂载

绑定挂载自Docker早期就存在。与相比,绑定挂载的功能有限。使用绑定挂载时,主机上的文件或目录将被挂载到容器中。该文件或目录通过其在主机上的绝对路径进行引用。相反,当您使用卷时,将在主机上的Docker存储目录中创建一个新目录,Docker将管理该目录的内容。

文件或目录不需要预先存在于Docker主机上。如果它尚不存在,则会按需创建。绑定挂载性能非常高,但它们依赖于主机文件系统具有可用的特定目录结构。如果您正在开发新的Docker应用程序,请考虑改用命名卷。您不能使用Docker CLI命令直接管理绑定挂载。

Bind mounts on the Docker host

提示

处理大型仓库或单体仓库,或使用虚拟文件系统,而这些文件系统已无法随着代码库的扩展而扩展?请查看同步文件共享。它通过使用同步文件系统缓存来增强绑定挂载性能,从而提供快速灵活的主机到虚拟机的文件共享。

选择-v或--mount标志

一般来说,--mount更明确且更详细。最大的区别在于-v语法将所有选项组合在一个字段中,而--mount语法则将它们分开。以下是每个标志语法的比较。

提示

新用户应使用--mount语法。经验丰富的用户可能更熟悉-v--volume语法,但建议使用--mount,因为研究表明它更容易使用。

  • -v--volume:由三个字段组成,用冒号字符(:)分隔。字段必须按正确的顺序排列,并且每个字段的含义并不立即显而易见。

    • 对于绑定挂载,第一个字段是**主机**上文件或目录的路径。
    • 第二个字段是文件或目录在容器中挂载的路径。
    • 第三个字段是可选的,它是一个以逗号分隔的选项列表,例如rozZ。这些选项将在下面讨论。
  • --mount:包含多个键值对,以逗号分隔,每个键值对由一个<key>=<value>元组组成。--mount语法比-v--volume更冗长,但键的顺序并不重要,标志的值更容易理解。

    • 挂载的type,可以是bindvolumetmpfs。本主题讨论绑定挂载,因此类型始终为bind
    • 挂载的source。对于绑定挂载,这是Docker守护进程主机上文件或目录的路径。可以指定为sourcesrc
    • destination的值为在容器中挂载文件或目录的路径。可以指定为destinationdsttarget
    • 如果存在readonly选项,则绑定挂载将以只读方式挂载到容器中
    • 如果存在bind-propagation选项,则会更改绑定传播。可以是rprivateprivatersharedsharedrslaveslave之一。
    • --mount标志不支持zZ选项来修改selinux标签。

下面的示例显示了--mount-v语法(在可能的情况下),并且--mount优先显示。

-v--mount行为之间的区别

由于-v--volume标志已成为Docker的长期组成部分,因此无法更改其行为。这意味着-v--mount之间存在一种不同的行为。

如果使用-v--volume绑定挂载Docker主机上尚不存在的文件或目录,则-v会为您创建端点。它始终被创建为目录。

如果使用--mount绑定挂载Docker主机上尚不存在的文件或目录,Docker不会自动为您创建它,而是会生成错误。

使用绑定挂载启动容器

假设您有一个目录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 stop devtest

$ docker container rm devtest

挂载到容器上的非空目录

如果将目录绑定挂载到容器中非空的目录,则该目录的现有内容将被绑定挂载隐藏。这可能很有用,例如,当您想测试应用程序的新版本而无需构建新镜像时。但是,这也可能令人意外,并且此行为与docker卷的行为不同。

此示例被设计成极端情况,但它用主机上的/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 stop devtest

$ docker container rm devtest

递归挂载

当绑定挂载本身包含挂载的路径时,默认情况下,这些子挂载也包含在绑定挂载中。此行为可以使用--mountbind-recursive选项进行配置。此选项仅支持--mount标志,不支持-v--volume

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

bind-recursive选项支持的值为

描述
enabled(默认)如果内核版本为5.12或更高版本,则递归地将只读挂载设置为只读。否则,子挂载为读写。
disabled忽略子挂载(不包含在绑定挂载中)。
writable子挂载为读写。
readonly子挂载为只读。需要内核版本5.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选项表示绑定挂载内容在多个容器之间共享。
  • Z选项表示绑定挂载内容是私有的且不共享的。

请谨慎使用这些选项。使用Z选项绑定挂载系统目录(例如/home/usr)会使您的主机无法运行,您可能需要手动重新标记主机文件。

重要

在服务中使用bind mounts时,selinux标签(:Z:z),以及:ro 将被忽略。详情请参见 moby/moby #32579

此示例设置z选项,以指定多个容器可以共享bind mount的内容。

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

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

将绑定挂载与compose一起使用

带有bind mount的单个Docker Compose服务如下所示

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

有关在Compose中使用`bind`类型卷的更多信息,请参见 Compose卷参考Compose卷配置参考

后续步骤