SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-30

Docker Compose volume:bind mount 與 named volume

比較 Docker Compose 的 bind mount 與 named volume,說明設定檔與資料的適用情境、權限錯誤陷阱,以及如何檢查、備份與遷移。

Bind mount 或 named volume:簡短答案

Docker Compose volume 分成兩種類型,選擇取決於檔案由誰管理。對於需要自行讀寫的檔案,例如設定、範本和靜態網站,請使用 bind mount。對於由應用程式管理的資料,例如資料庫檔案、搜尋索引和上傳媒體,請使用 named volume。bind mount 指向主機上的路徑,您可以直接在編輯器中開啟。named volume 是由 Docker 建立並追蹤的儲存空間,您必須透過 Docker 存取。

兩者都會出現在服務內相同的 volumes: 索引下,因此容易混淆。差異在於冒號左側。左側以 ./ 開頭時,代表主機路徑,因此是 bind mount。其他內容則是名稱,因此是 named volume;該名稱也必須在頂層的 volumes: 區塊中宣告。

Compose 檔案中的兩種語法

services:
  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: changeme
    volumes:
      - pgdata:/var/lib/postgresql/data
  web:
    image: nginx:1.27
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./site:/usr/share/nginx/html:ro

volumes:
  pgdata:

pgdata:/var/lib/postgresql/data 是命名 volume。./nginx.conf:/etc/nginx/nginx.conf 是 bind mount,而 :ro 會以唯讀方式掛載。對於容器不應改寫的設定檔,這是正確的預設值。若省略頂層的 volumes: 項目,Compose 會以 service "db" refers to undefined volume pgdata 終止。

啟動服務,並列出 Docker 建立的項目:

docker compose up -d
docker volume ls

此 volume 不叫做 pgdata,而是叫做 <project>_pgdata;專案名稱預設為 compose 檔案所在目錄的名稱。若目錄名稱為 myapp,則會產生 myapp_pgdata。這點很重要,因為重新命名目錄會產生一個新的空 volume,應用程式看起來就像遺失了資料。實際上資料並未遺失:舊的 volume 仍會由 docker volume ls 列出。請在 compose 檔案中使用 name: 固定名稱;如果目錄可能移動,也可以設定 COMPOSE_PROJECT_NAME。這類設定應與其他 Compose 環境檔案與 secrets 一起管理。

為何權限錯誤只會影響 bind mount

這是最重要的實務差異,原因來自一項規則:第一次使用時,空的 named volume 會從映像檔初始化,而 bind mount 不會。

當 Docker 將空的 named volume 掛載到映像檔中原本已有內容的目錄時,會把這些內容複製到 volume,並保留映像檔設定的擁有者與權限模式。官方 Postgres 映像檔提供的 /var/lib/postgresql/data 由專用的 postgres 使用者擁有,因此建立的 volume 也會由相同的數值 ID 擁有,資料庫便能啟動。

bind mount 的行為相反。主機上的內容就是容器看到的內容,包括擁有者,而映像檔在該路徑的內容會被隱藏。若主機上的目錄不存在,Docker daemon 會建立該目錄,而 daemon 以 root 身分執行,因此會得到由 root:root 擁有的目錄。以非 root 使用者執行的容器程序便無法寫入:

PermissionError: [Errno 13] Permission denied: '/data/app.db'

解決方法是讓數值 ID 一致。bind mount 的擁有權是依數值 user ID 比對,而不是依名稱比對,因為容器有自己的 /etc/passwd。容器內名為 app 的使用者,對主機沒有任何意義。兩側的 uid 1000 都代表 uid 1000。

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id 會輸出容器程序實際執行時使用的 uid。將主機目錄的擁有者改為該數值,或在服務中使用 user: "1000:1000",將容器固定為你的數值。對自行撰寫的應用程式而言,固定 user: 的方式較簡潔。對並非自行撰寫的映像檔而言,修改主機目錄的擁有者較安全,因為某些映像檔會先以 root 身分啟動 entrypoint,再降低權限,並預期底層檔案具備特定擁有權。

另外還有兩個容易踩到的問題。在 Fedora、RHEL 及其他啟用 SELinux(security-enhanced Linux)強制模式的系統上,bind mount 在重新標籤前會遭到拒絕。因此,容器共用的路徑請加入 :z;僅供單一容器使用的路徑請加入 :Z,寫法為 - ./data:/data:Z。此外,掛載單一檔案而非目錄時,若編輯器以替換檔案的方式修改檔案,而不是直接寫入原檔,bind mount 也會失效,因為掛載會追蹤原本的 inode。容器會持續看到舊內容,直到重新啟動。若檔案經常需要編輯,請改為掛載其父目錄。

效能:差異真正存在的地方

在 Linux 伺服器上,兩種方式都會經過相同的核心路徑,因此吞吐量差異很小,不應以此作為選擇依據。使用預設 local 驅動程式的具名磁碟區,會與 Docker 的其他資料位於相同檔案系統中的 /var/lib/docker/volumes/ 下;繫結掛載則位於您指定的位置。

差異會出現在 macOS 和 Windows 上的 Docker Desktop,因為容器是在虛擬機器內執行。這裡的繫結掛載必須透過檔案共享層,從主機檔案系統跨入虛擬機器。對於包含大量小型檔案操作的工作負載,例如 Node.js 相依套件樹或 PHP framework 快取,效能會明顯下降。具名磁碟區留在虛擬機器內,不必承擔這項成本。因此,許多開發用 compose 檔案會繫結掛載來源目錄,但在 node_modules 上宣告具名磁碟區。

另一項實際差異是資料寫入的位置。繫結掛載至 /mnt/backup 時,資料會寫入該磁碟。具名磁碟區則會寫入存放 /var/lib/docker 的檔案系統;在 VPS 上,這通常是 root 磁碟。若資料庫在具名磁碟區中持續成長,會佔滿與系統日誌共用的磁碟。請在問題演變成事故前檢查:

docker system df -v
df -h /var/lib/docker

docker system df -v 會列出所有磁碟區及其大小,並標示目前沒有任何容器再參照的磁碟區。

檢查具名 volume

具名 volume 不是黑箱。使用 Docker 查詢其位置:

docker volume inspect myapp_pgdata

Mountpoint 欄位會提供實際的主機路徑,通常是 /var/lib/docker/volumes/myapp_pgdata/_data。您可以使用 sudo ls 讀取該路徑,這適合快速檢查。但不要將其視為可直接編輯檔案的位置。以 root 身分在該處寫入檔案,會再次造成上述的擁有權問題;而且該路徑是 local driver 的實作細節,其他 volume driver 不一定具備相同配置。

安全的檢視方式是使用掛載該 volume 的一次性容器:

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

這種方式適用於任何 driver,看到的權限與實際容器相同,並且不會留下任何內容,因為使用了 --rm

備份各種類型

bind mount 是一般目錄,因此任何檔案層級的備份工具都能處理。將備份目標指向主機路徑即可。named volume 需要多一個步驟,因為工具必須進入其中。將 volume 與主機目錄掛載到同一個短期容器中,然後建立封存檔:

docker run --rm \
  -v myapp_pgdata:/data:ro \
  -v "$PWD":/backup \
  alpine tar czf /backup/pgdata.tar.gz -C /data .

將內容還原到新的 volume,方式相反:

docker volume create myapp_pgdata_restored
docker run --rm \
  -v myapp_pgdata_restored:/data \
  -v "$PWD":/backup \
  alpine tar xzf /backup/pgdata.tar.gz -C /data

tar 會在容器內以 root 身分執行時保留數字所有權,這能確保還原後的 volume 可供應用程式使用。

以下警告適用於這兩種類型。在資料庫執行期間複製其檔案,得到的是持續變動中的資料封存檔,還原後可能會處於損毀狀態。請先停止服務,或使用資料庫本身的工具匯出,如 docker compose exec -T db pg_dump -U postgres appdb > appdb.sql 所示。這會產生純文字檔,之後即可將其與 compose 檔案一併納入一般的 加密 restic 備份流程

將 bind mount 遷移至 named volume

這項作業是複製,而不是重新命名,約需 1 分鐘。

docker compose down
docker volume create myapp_pgdata
docker run --rm \
  -v "$PWD/data":/from \
  -v myapp_pgdata:/to \
  alpine sh -c 'cp -a /from/. /to/'

cp -a 會保留擁有者、模式與時間戳記,因此原本能讀取舊目錄的容器使用者,仍能讀取新的 volume。接著將服務改為使用 pgdata:/var/lib/postgresql/data,在頂層的 volumes: 區塊中加入 pgdata,執行 docker compose up -d,並在刪除舊目錄前查看應用程式的日誌。反向操作時,使用相同的指令,但交換 /from/to

測試時請注意一點。docker compose down 不會處理 named volume,但 docker compose down -v 會刪除專案宣告的所有 named volume,而且無法復原。bind mount 在這兩種操作後都會保留,因為 Docker 從未擁有該目錄。若你尚不熟悉這些生命週期指令,請參閱 VPS 的 Docker Compose 基礎指南,其中會逐步說明這些指令。

依服務逐一選擇

先確認檔案由誰寫入。在文字編輯器中修改並提交至 git 的設定,應使用 bind mount,掛載為 :ro,因為你需要直接查看並進行版本管理。從不手動開啟的應用程式狀態資料,應使用 named volume,因為 Docker 會正確設定權限,且資料不依賴主機路徑。

媒體資料屬於混合情況。相片庫由應用程式寫入,但也由你管理,而且通常容量很大,適合放在指定磁碟上。將它 bind mount 至該磁碟上的路徑,並一次明確設定擁有權。多數自架服務堆疊最後都採用這種模式:資料庫與快取使用 named volume,設定使用 bind mount,重要的大型目錄也使用 bind mount。支援服務 在 VPS 上執行的 Chatwoot 正是如此:Postgres 使用 named volume,而上傳的附件放在可指定給備份工具的路徑上。

FAQ

bind mount 與 named volume 有何差異?

bind mount 會將主機上的路徑對映到容器中,因此兩端會看到相同的目錄,您也能使用一般工具編輯該目錄。named volume 是由 Docker 建立及管理的儲存空間,透過名稱參照,並在最上層的 volumes: 區塊中宣告。實務上的區分在於管理責任:由您維護的設定使用 bind mount,由應用程式維護的資料使用 named volume。

為什麼使用 bind mount 會出現「permission denied」,但使用 named volume 不會?

空的 named volume 會從映像檔初始化,因此會繼承映像檔設定的擁有權,容器使用者也能寫入其中。bind mount 會完全呈現主機目錄的狀態;如果 Docker 必須建立該目錄,目錄會由 root 擁有。執行 docker compose exec <service> id 查看容器使用的數值 ID,再執行 sudo chown -R <uid>:<gid> 變更主機目錄,或在服務上設定 user: "1000:1000"

Docker 將 named volume 儲存在哪個磁碟位置?

使用預設的 local driver 時,named volume 會儲存在 /var/lib/docker/volumes/<volume>/_data 下,而 docker volume inspect <volume> 會列出確切的 Mountpoint。如果需要檢查內容,可以讀取該位置;但只能透過容器寫入,因為在主機上以 root 編輯會以容器預期之外的方式變更擁有權。

如何備份 named volume?

執行一個短期容器,同時掛載該 volume 與主機目錄,再使用 docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data . 將其中一端的內容封存到另一端。對於資料庫,應使用資料庫本身的工具進行 dump,而不是複製正在使用的檔案,因為在寫入進行中取得的檔案副本,還原後可能會處於損毀狀態。

docker compose down 會刪除我的 volume 嗎?

docker compose down 會移除容器與網路,但會保留 named volume。docker compose down -v 也會刪除專案宣告的所有 named volume,且無法復原。兩個指令都不會移除 bind mount,因為該目錄屬於主機,而不是 Docker。