Skip to content

绑定挂载

原文:https://docs.docker.com/storage/bind-mounts/

绑定挂载自 Docker 早期以来就已存在。与相比,绑定挂载的功能有限。使用绑定挂载时,宿主机上的文件或目录会被挂载到容器中。该文件或目录通过宿主机上的绝对路径引用。相比之下,使用卷时,会在宿主机上的 Docker 存储目录中创建一个新目录,并由 Docker 管理该目录的内容。

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

Docker主机上的绑定挂载

提示

是否在处理大型仓库或单体仓库,或者虚拟文件系统不再能够跟上您的代码库的规模? 查看同步文件共享。它通过使用同步文件系统缓存增强绑定挂载的性能,提供快速灵活的主机到虚拟机文件共享。

选择-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标志不支持用于修改 selinux 标签的zZ选项。

以下示例显示了可能的--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容器,否则您不能同时运行它们。

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

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

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

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

停止容器:

console
docker container stop devtest

docker container rm devtest

在容器中的非空目录上挂载

如果您将目录绑定挂载到容器中的非空目录上,绑定挂载会遮盖该目录的现有内容。这可能是有益的,例如当您想测试您的应用程序的新版本而不构建新镜像时。然而,这也可能令人惊讶,这种行为与docker 卷不同。

这个例子是人为的极端,但它用宿主机上的/tmp/目录替换了容器的/usr/目录的内容。在大多数情况下,这将导致容器无法正常工作。

--mount

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

console
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".

容器被创建但未启动。移除它:

console
docker container rm broken-container

使用只读绑定挂载

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

此示例修改了上面的示例,但将目录作为只读绑定挂载挂载,通过在容器中的挂载点后添加ro(默认为空)来实现。如果存在多个选项,请用逗号分隔它们。

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

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

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

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

停止容器:

console
docker container stop devtest

docker container rm devtest

递归挂载

当您绑定挂载一个路径时,如果该路径本身包含挂载,则这些子挂载默认也包括在绑定挂载中。这种行为是可配置的,使用--mountbind-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 桌面。

传播设置描述
shared原始挂载的子挂载对副本挂载可见,副本挂载的子挂载也传播到原始挂载。
slave类似于共享挂载,但只有一个方向。如果原始挂载公开了一个子挂载,副本挂载可以看到它。然而,如果副本挂载公开了一个子挂载,原始挂载看不到它。
private挂载是私有的。其中的子挂载不会对副本挂载公开,副本挂载的子挂载也不会对原始挂载公开。
rshared与共享相同,但传播也扩展到原始或副本挂载点内嵌的所有挂载点。
rslave与从属相同,但传播也扩展到原始或副本挂载点内嵌的所有挂载点。
rprivate默认。与私有相同,意味着原始或副本挂载点内的任何挂载点都不会在任何方向上传播。

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

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

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

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

console
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

现在如果您在/app/foo/创建文件,/app2/foo/也会存在。

配置 selinux 标签

如果您使用selinux,您可以添加zZ选项来修改挂载到容器中的宿主文件

或目录的 selinux 标签。这会影响宿主机上的文件或目录本身,并可能在 Docker 范围之外产生后果。

  • z选项表明绑定挂载内容是多个容器共享的。
  • Z选项表明绑定挂载内容是私有的,未共享。

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

重要

当使用服务时,绑定挂载的 selinux 标签(:Z:z)以及ro都会被忽略。详情见 moby/moby #32579

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

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

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

在 compose 中使用绑定挂载

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

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

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

下一步