绑定挂载
当您使用绑定挂载时,主机上的文件或目录从主机挂载到容器中。相比之下,当您使用卷时,在主机上 Docker 的存储目录中创建一个新目录,并由 Docker 管理该目录的内容。
何时使用绑定挂载
绑定挂载适用于以下用例类型:
在 Docker 主机上的开发环境和容器之间共享源代码或构建工件。
当您想在容器中创建或生成文件并将其持久化到主机的文件系统上时。
将配置文件从主机共享到容器。这是 Docker 默认向容器提供 DNS 解析的方式,通过将主机上的
/etc/resolv.conf
挂载到每个容器中。
绑定挂载也适用于构建:您可以将主机上的源代码绑定挂载到构建容器中,以测试、Lint 或编译项目。
在现有数据上绑定挂载
如果您将文件或目录绑定挂载到容器中已经存在文件或目录的目录中,则现有文件将被挂载遮蔽。这类似于您将文件保存到 Linux 主机上的 /mnt
中,然后将 USB 驱动器挂载到 /mnt
中。/mnt
的内容将被 USB 驱动器的内容遮蔽,直到 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
目录内运行此命令。$(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
挂载到容器中的非空目录
如果您将目录绑定挂载到容器中的非空目录,该目录的现有内容将被绑定挂载遮蔽。这可能很有益,例如当您想测试应用程序的新版本而无需构建新镜像时。但是,这也可能令人意外,而且这种行为与卷的行为不同。
此示例是为了突出极端情况而设计的,它将容器的 /usr/
目录内容替换为主机机器上的 /tmp/
目录内容。在大多数情况下,这会导致容器无法正常工作。
--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 Engine 会尽最大努力使子挂载也变为只读。这被称为递归只读挂载。递归只读挂载需要 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 | 默认值。与 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,您可以添加 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:
有关将 bind
类型的卷与 Compose 一起使用的更多信息,请参阅 Compose 卷参考和 Compose 卷配置参考。