繫結掛載

當您使用繫結掛載時,主機會上的檔案或目錄會從主機掛載到容器中。相比之下,當您使用卷時,會在 Docker 主機上的 Docker 儲存目錄內建立一個新目錄,並由 Docker 管理該目錄的內容。

何時使用繫結掛載

繫結掛載適用於以下型別的用例

  • 在 Docker 主機上的開發環境和容器之間共享原始碼或構建產物。

  • 當您想在容器中建立或生成檔案,並將檔案持久化到主機的檔案系統上時。

  • 將主機上的配置檔案共享給容器。Docker 預設就是透過這種方式為容器提供 DNS 解析的,即將主機上的 /etc/resolv.conf 掛載到每個容器中。

繫結掛載也可用於構建:您可以將主機上的原始碼繫結掛載到構建容器中,以測試、檢查或編譯專案。

在現有資料上進行繫結掛載

如果您將檔案或目錄繫結掛載到容器中已有檔案或目錄的目錄中,原有的檔案將被掛載所遮蔽。這類似於您在 Linux 主機上將檔案儲存到 /mnt,然後將一個 USB 驅動器掛載到 /mnt。在 USB 驅動器被解除安裝之前,/mnt 的內容將被 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 標誌由多個鍵值對組成,用逗號分隔,每個鍵值對由一個 <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

遞迴掛載

當您繫結掛載一個本身包含掛載的路徑時,這些子掛載預設也會被包含在繫結掛載中。此行為是可配置的,使用 --mountbind-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,可以新增 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 關於卷配置的參考

後續步驟