绑定挂载
当您使用绑定挂载时,主机会上的文件或目录会从主机挂载到容器中。相比之下,当您使用卷时,会在 Docker 主机上的 Docker 存储目录内创建一个新目录,并由 Docker 管理该目录的内容。
何时使用绑定挂载
绑定挂载适用于以下类型的用例
在 Docker 主机上的开发环境和容器之间共享源代码或构建产物。
当您想在容器中创建或生成文件,并将文件持久化到主机的文件系统上时。
将主机上的配置文件共享给容器。Docker 默认就是通过这种方式为容器提供 DNS 解析的,即将主机上的
/etc/resolv.conf挂载到每个容器中。
绑定挂载也可用于构建:您可以将主机上的源代码绑定挂载到构建容器中,以测试、检查或编译项目。
在现有数据上进行绑定挂载
如果您将文件或目录绑定挂载到容器中已有文件或目录的目录中,原有的文件将被挂载所遮蔽。这类似于您在 Linux 主机上将文件保存到 /mnt,然后将一个 USB 驱动器挂载到 /mnt。在 USB 驱动器被卸载之前,/mnt 的内容将被 USB 驱动器的内容所遮蔽。
对于容器,没有直接的方法可以移除挂载以再次显示被遮蔽的文件。最好的选择是重新创建没有该挂载的容器。
注意事项和限制
默认情况下,绑定挂载对主机上的文件具有写权限。
使用绑定挂载的一个副作用是,您可以通过在容器中运行的进程来更改主机文件系统,包括创建、修改或删除重要的系统文件或目录。这种能力可能会带来安全隐患。例如,它可能会影响主机系统上的非 Docker 进程。
您可以使用
readonly或ro选项来防止容器向挂载点写入数据。绑定挂载是创建在 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 目录内运行该命令。在 Linux 或 macOS 主机上,$(pwd) 子命令会扩展为当前工作目录。如果您在 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
挂载到容器中的非空目录
如果您将一个目录绑定挂载到容器中的一个非空目录,该目录的现有内容会被绑定挂载所遮蔽。这可能是有益的,例如当您想测试一个新版本的应用程序而不需要构建新镜像时。然而,这也可能令人意外,并且此行为与卷的行为不同。
这个例子为了极端而设计,它用主机上的 /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
递归挂载
当您绑定挂载一个本身包含挂载的路径时,这些子挂载默认也会被包含在绑定挂载中。此行为是可配置的,使用 --mount 的 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 | 与从属相同,但传播也扩展到原始或副本挂载点内嵌套的挂载点,并从这些挂载点传播。 |
rprivate | 默认值。与私有相同,意味着在原始或副本挂载点内的任何挂载点都不会在任一方向上传播。 |
在您可以在挂载点上设置绑定传播之前,主机文件系统需要已经支持绑定传播。
有关绑定传播的更多信息,请参阅 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,可以添加 z 或 Z 选项来修改挂载到容器中的主机文件或目录的 SELinux 标签。这会影响主机本身的文件或目录,并可能产生超出 Docker 范围的后果。
z选项表示绑定挂载内容在多个容器之间共享。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 关于卷配置的参考。