繫結掛載
當您使用繫結掛載時,主機上的檔案或目錄會從主機掛載到容器中。相較之下,當您使用磁碟區 (volume) 時,Docker 會在主機的儲存目錄中建立一個新目錄,並由 Docker 管理該目錄的內容。
何時使用繫結掛載
繫結掛載適用於以下使用案例:
在 Docker 主機上的開發環境和容器之間共用原始碼或建置產物。
當您想在容器中建立或產生檔案,並將這些檔案儲存到主機的檔案系統上時。
將組態檔從主機共用給容器。這也是 Docker 預設為容器提供 DNS 解析的方式,透過將主機上的
/etc/resolv.conf掛載到每個容器中。
繫結掛載也可用於建置:您可以將原始碼從主機繫結掛載到建置容器中,以測試、檢查程式碼或編譯專案。
覆蓋現有資料的繫結掛載
如果您將檔案或目錄繫結掛載到容器中已存在檔案或目錄的目錄,則現有的檔案會被掛載遮蔽。這就像在 Linux 主機上將檔案儲存到 /mnt,然後將 USB 隨身碟掛載到 /mnt 中一樣。/mnt 的內容會被 USB 隨身碟的內容遮蔽,直到 USB 隨身碟被卸載。
對於容器,沒有直接的方法可以移除掛載以再次顯示被遮蔽的檔案。您最好的選擇是重新建立沒有掛載的容器。
考量與限制
繫結掛載預設對主機上的檔案具有寫入權限。
使用繫結掛載的一個副作用是,您可以透過容器中執行的程序更改主機檔案系統,包括建立、修改或刪除重要的系統檔案或目錄。此功能可能帶來安全隱患。例如,它可能會影響主機系統上非 Docker 的程序。
您可以使用
readonly或ro選項來防止容器寫入掛載點。繫結掛載是建立在 Docker 守護程式 (daemon) 主機上,而不是客戶端。
如果您使用的是遠端 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
掛載至容器中的非空目錄
如果您將目錄繫結掛載到容器中的非空目錄,該目錄的現有內容將被繫結掛載遮蔽。這可能是有益的,例如當您想在不建置新映像檔的情況下測試應用程式的新版本時。然而,這也可能令人感到意外,且此行為與磁碟區 (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
遞迴掛載
當您繫結掛載一個本身包含掛載點的路徑時,這些子掛載點預設也會包含在繫結掛載中。此行為可透過 --mount 的 bind-recursive 選項進行設定。此選項僅支援 --mount 旗標,而不支援 -v 或 --volume。
如果繫結掛載是唯讀的,Docker 引擎會盡力嘗試讓子掛載點也成為唯讀。這稱為遞迴唯讀掛載。遞迴唯讀掛載需要 Linux 核心版本 5.12 或更高版本。如果您運行的是較舊的核心版本,子掛載點預設會自動掛載為讀寫。嘗試在 5.12 之前的核心版本上使用 bind-recursive=readonly 選項將子掛載點設定為唯讀,會導致錯誤。
bind-recursive 選項支援的值為:
| 值 | 描述 |
|---|---|
enabled (預設) | 如果核心版本為 5.12 或更高,唯讀掛載會遞迴地設為唯讀。否則,子掛載點是讀寫的。 |
disabled | 子掛載點會被忽略 (不包含在繫結掛載中)。 |
writable | 子掛載點是讀寫的。 |
readonly | 子掛載點是唯讀的。需要核心版本 5.12 或更高。 |
設定繫結傳播
繫結掛載和磁碟區 (volumes) 的繫結傳播預設為 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 磁碟區配置參考。
下一步
- 瞭解磁碟區 (volumes)。
- 瞭解tmpfs 掛載。
- 瞭解儲存驅動程式。