绑定挂载
原文:https://docs.docker.com/storage/bind-mounts/
绑定挂载自 Docker 早期以来就已存在。与卷相比,绑定挂载的功能有限。使用绑定挂载时,宿主机上的文件或目录会被挂载到容器中。该文件或目录通过宿主机上的绝对路径引用。相比之下,使用卷时,会在宿主机上的 Docker 存储目录中创建一个新目录,并由 Docker 管理该目录的内容。
文件或目录不需要已经存在于 Docker 宿主机上。如果尚不存在,它将按需创建。绑定挂载性能非常好,但它依赖于宿主机文件系统具有特定的目录结构。如果您正在开发新的 Docker 应用程序,请考虑使用命名卷。您无法使用 Docker CLI 命令直接管理绑定挂载。
提示
是否在处理大型仓库或单体仓库,或者虚拟文件系统不再能够跟上您的代码库的规模? 查看同步文件共享。它通过使用同步文件系统缓存增强绑定挂载的性能,提供快速灵活的主机到虚拟机文件共享。
选择-v 或--mount 标志
通常,--mount
更明确且冗长。最大的区别是-v
语法将所有选项合并在一个字段中,而--mount
语法将它们分开。这里是每个标志的语法比较。
提示
新用户应使用
--mount
语法。经验丰富的用户可能更熟悉-v
或--volume
语法,但鼓励使用--mount
,因为研究表明它更易于使用。
-v
或--volume
:由三个用冒号(:
)分隔的字段组成。字段必须按正确的顺序,每个字段的含义并不立即明显。- 在绑定挂载的情况下,第一个字段是宿主机上文件或目录的路径。
- 第二个字段是容器中文件或目录挂载的路径。
- 第三个字段是可选的,是一个逗号分隔的选项列表,例如
ro
、z
和Z
。这些选项将在下文中讨论。
--mount
:由多个用逗号分隔的键值对组成,每个键值对都由一个<key>=<value>
元组组成。--mount
语法比-v
或--volume
更冗长,但键的顺序不重要,标志的值更易于理解。- 挂载的
type
,可以是bind
、volume
或tmpfs
。本主题讨论绑定挂载,因此类型始终是bind
。 - 挂载的
source
。对于绑定挂载,这是 Docker 守护进程主机上文件或目录的路径。可以指定为source
或src
。 destination
取其值为容器中文件或目录挂载的路径。可以指定为destination
、dst
或target
。- 如果存在
readonly
选项,它会导致绑定挂载以只读方式挂载到容器中。 - 如果存在
bind-propagation
选项,
- 挂载的
它会改变绑定传播。可能的值包括rprivate
、private
、rshared
、shared
、rslave
、slave
。
--mount
标志不支持用于修改 selinux 标签的z
或Z
选项。
以下示例显示了可能的--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 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 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 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
递归挂载
当您绑定挂载一个路径时,如果该路径本身包含挂载,则这些子挂载默认也包括在绑定挂载中。这种行为是可配置的,使用--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 桌面。
传播设置 | 描述 |
---|---|
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
现在如果您在/app/foo/
创建文件,/app2/foo/
也会存在。
配置 selinux 标签
如果您使用selinux
,您可以添加z
或Z
选项来修改挂载到容器中的宿主文件
或目录的 selinux 标签。这会影响宿主机上的文件或目录本身,并可能在 Docker 范围之外产生后果。
z
选项表明绑定挂载内容是多个容器共享的。Z
选项表明绑定挂载内容是私有的,未共享。
使用这些选项时要格外小心。绑定挂载系统目录,如/home
或/usr
,并使用Z
选项会使您的宿主机无法操作,您可能需要手动重新标记宿主机文件。
重要
当使用服务时,绑定挂载的 selinux 标签(
:Z
和:z
)以及ro
都会被忽略。详情见 moby/moby #32579。
此示例设置了z
选项,以指定多个容器可以共享绑定挂载的内容:
使用--mount
标志无法修改 selinux 标签。
docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app:z \
nginx:latest
在 compose 中使用绑定挂载
带有绑定挂载的单个 Docker Compose 服务如下所示:
services:
frontend:
image: node:lts
volumes:
- type: bind
source: ./static
target: /opt/app/static
volumes:
myapp:
有关在 Compose 中使用bind
类型卷的更多信息,请参阅 Compose 参考卷。 以及 Compose 卷配置参考。