绑定挂载

当你使用绑定挂载时,主机上的文件或目录会从主机挂载到容器中。相比之下,当你使用卷时,会在主机上的Docker存储目录中创建一个新目录,并且Docker管理该目录的内容。

何时使用绑定挂载

绑定挂载适用于以下类型的使用场景:

  • 在Docker主机上的开发环境和容器之间共享源代码或构建产物。

  • 当你想在容器中创建或生成文件并将文件持久化到主机的文件系统时。

  • 将配置文件从主机共享到容器。这是Docker默认通过将主机的/etc/resolv.conf挂载到每个容器中,为容器提供DNS解析的方式。

绑定挂载也可用于构建:您可以将主机上的源代码绑定挂载到构建容器中,以测试、lint 或编译项目。

绑定挂载到现有数据上

如果你将文件或目录绑定挂载到容器中的一个目录中,而该目录中已经存在文件或目录,那么预先存在的文件将被挂载所覆盖。这类似于你在Linux主机上将文件保存到/mnt,然后将USB驱动器挂载到/mnt/mnt的内容将被USB驱动器的内容覆盖,直到USB驱动器被卸载。

使用容器时,没有直接的方法可以移除挂载以再次显示被隐藏的文件。您的最佳选择是在没有挂载的情况下重新创建容器。

考虑因素和限制

  • 绑定挂载默认对主机上的文件具有写访问权限。

    使用绑定挂载的一个副作用是,您可以通过在容器中运行的进程更改主机文件系统,包括创建、修改或删除重要的系统文件或目录。这种能力可能会带来安全影响。例如,它可能会影响主机系统上的非Docker进程。

    你可以使用readonlyro选项来防止容器写入挂载点。

  • 绑定挂载是创建到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 标志由多个键值对组成,用逗号分隔,每个键值对由一个 = 元组组成。键的顺序不重要。

$ docker run --mount type=bind,src=<host-path>,dst=<container-path>[,<key>=<value>...]

--mount type=bind 的有效选项包括:

OptionDescription
source, srcThe location of the file or directory on the host. This can be an absolute or relative path.
destination, dst, targetThe path where the file or directory is mounted in the container. Must be an absolute path.
readonly, roIf present, causes the bind mount to be 作为只读挂载到容器中.
bind-propagationIf present, changes the 绑定传播.
Example
$ docker run --mount type=bind,src=.,dst=/project,ro,bind-propagation=rshared

--volume 的选项

--volume-v 标志由三个字段组成,用冒号字符 (:) 分隔。字段必须按正确的顺序排列。

$ docker run -v <host-path>:<container-path>[:opts]

第一个字段是主机上要绑定挂载到容器中的路径。第二个字段是文件或目录在容器中挂载的路径。

第三个字段是可选的,是一个逗号分隔的选项列表。对于带有绑定挂载的--volume,有效的选项包括:

OptionDescription
readonly, roIf present, causes the bind mount to be 作为只读挂载到容器中.
z, ZConfigures SELinux labeling. See 配置SELinux标签
rprivate (default)Sets bind propagation to rprivate for this mount. See 配置绑定传播.
privateSets bind propagation to private for this mount. See 配置绑定传播.
rsharedSets bind propagation to rshared for this mount. See 配置绑定传播.
sharedSets bind propagation to shared for this mount. See 配置绑定传播.
rslaveSets bind propagation to rslave for this mount. See 配置绑定传播.
slaveSets bind propagation to slave for this mount. See 配置绑定传播.
Example
$ 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

挂载到容器中的非空目录

如果你将一个目录绑定挂载到容器中的一个非空目录,该目录的现有内容将被绑定挂载所遮蔽。这可能是有益的,例如当你想测试新版本的应用程序而不构建新镜像时。然而,这也可能令人惊讶,并且这种行为与volumes的行为不同。

这个例子是刻意设计的极端情况,但将容器的/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

递归挂载

当你绑定挂载一个本身包含挂载的路径时,默认情况下这些子挂载也会包含在绑定挂载中。这种行为是可配置的,使用--mountbind-recursive选项。此选项仅支持--mount标志,不支持-v--volume

如果绑定挂载是只读的,Docker 引擎会尽力使子挂载也变为只读。这被称为递归只读挂载。递归只读挂载需要 Linux 内核版本 5.12 或更高版本。如果您运行的是较旧的内核版本,子挂载默认会自动挂载为读写模式。尝试在内核版本早于 5.12 的情况下使用 bind-recursive=readonly 选项将子挂载设置为只读,会导致错误。

bind-recursive 选项支持的值有:

ValueDescription
enabled (default)Read-only mounts are made recursively read-only if kernel is v5.12 or later. Otherwise, submounts are read-write.
disabledSubmounts are ignored (not included in the bind mount).
writableSubmounts are read-write.
readonlySubmounts are read-only. Requires kernel v5.12 or later.

配置绑定传播

绑定传播默认设置为rprivate,适用于绑定挂载和卷。它仅可配置用于绑定挂载,并且仅在Linux主机上。绑定传播是一个高级主题,许多用户永远不需要配置它。

绑定传播指的是在给定的绑定挂载中创建的挂载是否可以传播到该挂载的副本。考虑一个挂载点/mnt,它也挂载在/tmp上。传播设置控制/tmp/a上的挂载是否也会在/mnt/a上可用。每个传播设置都有一个递归对应项。在递归的情况下,考虑/tmp/a也被挂载为/foo。传播设置控制/mnt/a和/或/tmp/a是否存在。

注意

挂载传播在Docker Desktop中不起作用。

Propagation settingDescription
sharedSub-mounts of the original mount are exposed to replica mounts, and sub-mounts of replica mounts are also propagated to the original mount.
slavesimilar to a shared mount, but only in one direction. If the original mount exposes a sub-mount, the replica mount can see it. However, if the replica mount exposes a sub-mount, the original mount cannot see it.
privateThe mount is private. Sub-mounts within it are not exposed to replica mounts, and sub-mounts of replica mounts are not exposed to the original mount.
rsharedThe same as shared, but the propagation also extends to and from mount points nested within any of the original or replica mount points.
rslaveThe same as slave, but the propagation also extends to and from mount points nested within any of the original or replica mount points.
rprivateThe default. The same as private, meaning that no mount points anywhere within the original or replica mount points propagate in either direction.

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

有关绑定传播的更多信息,请参阅 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,可以添加zZ选项来修改挂载到容器中的主机文件或目录的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参考卷配置

下一步