--- outline: [2,4] --- # 绑定挂载 使用绑定挂载时,主机上的文件或目录将从主机挂载到容器中。 相比之下,当您使用卷时,会在主机上的 Docker 存储目录中创建一个新目录,并且 Docker 会管理该目录的内容。 ## 何时使用 bind 挂载 绑定挂载适用于以下类型的用例: - 在 Docker 主机上的开发环境和容器之间共享源代码或构建构件。 - 当您想在容器中创建或生成文件并将文件持久化到主机的文件系统上时。 - 将配置文件从主机共享到容器。就是这样 默认情况下,Docker 通过挂载 `/etc/resolv.conf` 从主机发送到每个容器中。 绑定挂载也可用于构建:您可以将主机中的挂载源代码绑定到构建容器中,以测试、lint 或编译项目。 ## 在现有数据上绑定挂载 如果你将挂载文件或目录绑定到容器中存在文件或目录的目录中,则预先存在的文件会被挂载遮挡。 这类似于将文件保存到 Linux 主机上的 `/mnt` 中,然后将 USB 驱动器挂载到 `/mnt` 中。 `/mnt` 的内容将被 USB 驱动器的内容遮挡,直到卸载 USB 驱动器。 对于容器,没有直接的方法可以删除挂载以再次显示被遮挡的文件。最好的选择是重新创建没有挂载的容器。 ## 注意事项和约束 - 默认情况下,绑定挂载对主机上的文件具有写入访问权限。 使用绑定挂载的一个副作用是,您可以通过容器中运行的进程更改主机文件系统,包括创建、修改或删除重要的系统文件或目录。此功能可能会产生安全隐患。例如,它可能会影响主机系统上的非 Docker 进程。 您可以使用 `readonly` 或 `ro` 选项来防止容器写入挂载。 - 绑定挂载是到 Docker 守护程序主机创建的,而不是客户端。 如果您使用的是远程 Docker 守护程序,则无法创建绑定挂载来访问容器中客户端计算机上的文件。 对于 Docker Desktop,守护程序在 Linux VM 中运行,而不是直接在本机主机上运行。Docker Desktop 具有透明处理绑定挂载的内置机制,允许您与虚拟机中运行的容器共享本机主机文件系统路径。 - 具有绑定挂载的容器与主机紧密绑定。 定挂载依赖于具有特定可用目录结构的主机文件系统。这种依赖性意味着,如果在没有相同目录结构的不同主机上运行,则具有绑定挂载的容器可能会失败。 ## Syntax 语法 要创建绑定挂载,您可以使用 `--mount` 或 `--volume` 标志。 ```bash docker run --mount type=bind,src=,dst= docker run --volume : ``` 通常,`--mount` 是首选。主要区别在于 `--mount` flag 更明确,并支持所有可用选项。 如果您使用 `--volume` 绑定挂载 Docker 主机上尚不存在的文件或目录,Docker 会自动在主机上为您创建该目录。它始终创建为目录。 如果主机上不存在指定的挂载路径,则 `--mount` 不会自动创建目录。相反,它会产生一个错误: ```bash 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` 标志由多个键值对组成,用逗号分隔,每个键值对由一个 `=` 元组组成。键的顺序并不重要。 ```bash docker run --mount type=bind,src=,dst=[,=...] ``` `--mount type=bind` 的有效选项包括: | 选项 | 描述 | | - | - | | `source`, `src` | 文件或目录在主机上的位置。这可以是绝对路径或相对路径。 | | `destination`, `dst`, `target` | 文件或目录在容器中挂载的路径。必须是绝对路径。 | | `readonly`, `ro` | 如果存在,则会导致 bind 挂载为 [以只读方式挂载到容器中](https://docs.docker.com/engine/storage/bind-mounts/#use-a-read-only-bind-mount)。 | | `bind-propagation` | 如果存在,则更改 [绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | ###### 例 ```bash docker run --mount type=bind,src=.,dst=/project,ro,bind-propagation=rshared ``` ### --volume 的选项 `--volume` 或 `-v` 标志由三个字段组成,用冒号字符 (` : `) 分隔。字段的顺序必须正确。 ```bash docker run -v :[:opts] ``` 第一个字段是主机上要将 mount 绑定到容器的路径。第二个字段是文件或目录在容器中挂载的路径。 第三个字段是可选的,是以逗号分隔的选项列表。带有绑定挂载的 `--volume` 的有效选项包括: | 选项 | 描述 | | - | - | | `readonly`, `ro` | 如果存在,则会导致 bind 挂载为 [以只读方式挂载到容器中](https://docs.docker.com/engine/storage/bind-mounts/#use-a-read-only-bind-mount)。 | | `z`, `Z` | 配置 SELinux 标签。看 [配置 SELinux 标签](https://docs.docker.com/engine/storage/bind-mounts/#configure-the-selinux-label) | | `rprivate` (default) | 将此挂载的 bind propagation 设置为 `rprivate。看` [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | | `private` | 将此挂载的 bind propagation 设置为 `private。看` [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | | `rshared` | 将此挂载的 bind propagation 设置为 rshared。看 [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | | `shared` | 将此挂载的 bind propagation 设置为 shared。看 [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | | `rslave` | 为此挂载将 bind propagation 设置为 rslave。看 [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | | `slave` | 将此挂载的 bind propagation 设置为 slave。看 [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | ###### 例 ```bash docker run -v .:/project:ro,rshared ``` ## 使用绑定挂载启动容器 考虑这样一种情况:您有一个目录 `source`,并且在构建源代码时,工件被保存到另一个目录 `source/target/` 中。 您希望构件可用于位于 `/app/` 的容器,并且希望容器每次在开发主机上构建源代码时都能访问新的构建。 使用以下命令绑定挂载 `target/` 目录中的 `/app/` 中。 从 `source` 目录中。子命令 `$(pwd)` 扩展为当前正在运行的 目录。 如果您使用的是 Windows,另请参阅 [Windows 上的路径转换](https://docs.docker.com/desktop/troubleshoot-and-support/troubleshoot/topics/)。 以下 `--mount` 和 `-v` 示例产生相同的结果。除非在运行第一个容器后删除 `devtest` 容器,否则无法同时运行这两个容器。 :::code-group ```bash [--mount] docker run -d \ -it \ --name devtest \ --mount type=bind,source="$(pwd)"/target,target=/app \ nginx:latest ``` ```bash [-v] docker run -d \ -it \ --name devtest \ -v "$(pwd)"/target:/app \ nginx:latest ``` ::: 使用 `docker inspect devtest` 验证是否已正确创建绑定挂载。查找 `Mounts` 部分: ```bash:line-numbers "Mounts": [ { "Type": "bind", "Source": "/tmp/source/target", "Destination": "/app", "Mode": "", "RW": true, "Propagation": "rprivate" } ], ``` 这表明挂载是`bind`挂载,它显示了正确的源和目标,它表明挂载是读写的,并且传播设置为 `rprivate`。 停止并移除容器: ```bash docker container rm -fv devtest ``` ### 挂载到容器上的非空目录中 如果将目录绑定挂载到容器上的非空目录中,则 目录的现有内容被 bind 挂载遮挡。 这可以是 有益的,例如当您想要测试应用程序的新版本时 而无需构建新映像。 然而,它也可能令人惊讶,而且这 行为与 [卷](https://docs.docker.com/engine/storage/volumes/)。 这个例子是人为的极端,但将容器的 `/usr/` 目录的内容替换为主机上的 `/tmp/` 目录。在大多数情况下,这会导致容器无法正常工作。 `--mount` 和 `-v` 示例具有相同的最终结果。 :::code-group ```bash [--mount] 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". ``` ```bash [-v] 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". ``` ::: 容器已创建,但未启动。删除它: ```bash docker container rm broken-container ``` ## 使用只读绑定挂载 对于某些开发应用程序,容器需要写入绑定挂载,因此更改将传播回 Docker 主机。在其他时候,容器只需要读取访问权限。 此示例修改了前一个示例,但通过将 `ro` 添加到选项列表(默认为空)中,在容器内的挂载点之后,将目录挂载为只读绑定挂载。如果存在多个选项,请用逗号分隔它们。 :::code-group ```bash [--mount] docker run -d \ -it \ --name devtest \ --mount type=bind,source="$(pwd)"/target,target=/app,readonly \ nginx:latest ``` ```bash [-v] docker run -d \ -it \ --name devtest \ -v "$(pwd)"/target:/app:ro \ nginx:latest ``` ::: 使用` docker inspect devtest `验证是否已正确创建绑定挂载。查找 `Mounts` 部分: ```bash:line-numbers "Mounts": [ { "Type": "bind", "Source": "/tmp/source/target", "Destination": "/app", "Mode": "ro", "RW": false, "Propagation": "rprivate" } ], ``` 停止并移除容器: ```bash docker container rm -fv devtest ``` ## 递归挂载 当您绑定挂载本身包含 mounts 的路径时,默认情况下,这些子挂载也包含在 bind 挂载中。 此行为是可配置的,使用 `--mount` 的 `bind-recursive` 选项。此选项仅支持 `--mount` 标志,而不支持 `-v` 或 `--volume`。 如果绑定挂载是只读的,则 Docker 引擎会尽力而为 将 submounts 也设为只读。 这称为递归 只读挂载。递归只读挂载需要 Linux 内核版本 5.12 或之后。 如果你运行的是较旧的内核版本,则 submounts 是 默认情况下,自动挂载为读写。 尝试将 submounts 设置为 在 5.12 之前的内核版本上为只读,使用 `bind-recursive=readonly` 选项,则会导致错误。 `bind-recursive` 选项支持的值为: | 选项 | 描述 | | - | - | | `enabled` (默认) | 如果内核为 v5.12 或更高版本,则只读挂载将递归为只读。否则,子挂载是读写的。| | `disabled` (禁用) | 子挂载将被忽略(不包括在 bind 挂载中)。 | | `writable` (写) | Submounts 是读写的。 | | `readonly` (只读) | Submounts 是只读的。需要内核 v5.12 或更高版本。 | ## 配置绑定传播 绑定挂载和卷的绑定传播默认为 `rprivate`。它只能针对绑定挂载进行配置,并且只能在 Linux 主机上进行配置。绑定传播是一个高级主题,许多用户从不需要配置它。 绑定传播是指在给定绑定挂载中创建的挂载是否可以传播到该挂载的副本。 考虑一个挂载点 `/mnt`,它也挂载在 `/tmp` 上。传播设置控制 `/tmp/a` 上的挂载是否也可以在 `/mnt/a` 上使用。 每个传播设置都有一个递归对位点。在递归的情况下,请考虑 `/tmp/a` 也挂载为 `/foo`。传播设置控制 `/mnt/a` 和/或 `/tmp/a` 是否存在。 :::warning 注意 挂载传播不适用于 Docker Desktop。 ::: | 传播设置 | 描述 | | - | - | | `shared ` (共享) | 原始挂载的子挂载会暴露给副本挂载,而副本挂载的子挂载也会传播到原始挂载。 | | `slave` (奴隶) | 类似于共享挂载,但仅在一个方向上。如果原始挂载公开了子挂载,则副本挂载可以看到它。但是,如果副本挂载公开了子挂载,则原始挂载无法看到它。 | | `private` (私人) | 挂载是私有的。其中的子挂载不会暴露给副本挂载,而副本挂载的子挂载也不会暴露给原始挂载。 | | `rshared` | 与 shared 相同,但传播也扩展到嵌套在任何原始或副本挂载点中的挂载点或从这些挂载点扩展。 | | `rslave` | 与 slave 相同,但传播也扩展到嵌套在任何原始或复制副本挂载点中的挂载点。 | | `rprivate` | 默认值。与 private 相同,这意味着原始挂载点或副本挂载点中的任何位置都不会沿任一方向传播。 | 在挂载点上设置绑定传播之前,主机文件系统需要已经支持绑定传播。 有关绑定传播的更多信息,请参阅 [共享子树的 Linux 内核文档](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt)。 以下示例将 `target/` 目录挂载到容器中两次,第二次挂载同时设置 `ro` 选项和 `rslave` 绑定传播选项。 `--mount` 和 `-v` 示例具有相同的结果。 :::code-group ```bash [--mount] 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 ``` ```bash [-v] docker run -d \ -it \ --name devtest \ -v "$(pwd)"/target:/app \ -v "$(pwd)"/target:/app2:ro,rslave \ nginx:latest ``` ::: 现在,如果你创建 `/app/foo/`,`/app2/foo/` 也存在。 ## 配置 SELinux 标签 如果您使用 SELinux,则可以添加 `z` 或 `Z` 选项来修改要挂载到容器中的主机文件或目录的 SELinux 标签。 这会影响主机本身的文件或目录,并可能产生超出 Docker 范围的后果。 - `z` 选项指示绑定挂载内容在多个容器之间共享。 - 该 `Z` 选项指示绑定挂载内容是私有且未共享的。 使用这些选项时要格外小心。使用 `Z` 选项绑定挂载系统目录(如 `/home` 或 `/usr`)会导致主机无法操作,您可能需要手动重新标记主机文件。 :::danger 重要 当将 bind 挂载与服务一起使用时,SELinux 标签( `:Z` 和 `:z`)以及 `:ro` 将被忽略。 看 [moby/moby #32579](https://github.com/moby/moby/issues/32579) 了解详情。 ::: 此示例设置 `z` 选项以指定多个容器可以共享绑定挂载的内容: 无法使用 `--mount` 标志修改 SELinux 标签。 ```bash docker run -d \ -it \ --name devtest \ -v "$(pwd)"/target:/app:z \ nginx:latest ``` ## 将绑定挂载与 Docker Compose 结合使用 具有绑定挂载的单个 Docker Compose 服务如下所示: ```yaml:line-numbers services: frontend: image: node:lts volumes: - type: bind source: ./static target: /opt/app/static volumes: myapp: ``` 有关在 Compose 中使用 `bind` 类型的卷的更多信息,请参阅 [在卷上编写引用](https://docs.docker.com/reference/compose-file/services/#volumes)。 和 [Compose 参考 关于卷配置](https://docs.docker.com/reference/compose-file/services/#volumes)。 ## 后续步骤 - 了解 [卷](/docs/storage/volumes)。 - 了解 [tmpfs 挂载](/docs/storage/tmpfs-bind.md)。 - 了解 [存储驱动程序](/docs/storage/drivers/)。