目錄

Docker Volume 教學:資料持久化與 Bind Mount

容器內寫入的檔案存在容器的可寫層,容器一刪除,資料就跟著消失。資料庫、使用者上傳的檔案這類需要保存的資料,必須放在容器外面。Docker 提供三種掛載方式:

類型資料存在哪裡適合
VolumeDocker 管理的區域資料庫、正式環境的持久資料
Bind mount主機上你指定的資料夾開發時同步程式碼、讀取主機設定檔
tmpfs記憶體暫存、不想寫入硬碟的資料

Volume:刪掉容器,資料還在

用 PostgreSQL 實際驗證。啟動資料庫,把資料存在名為 pgdata 的 Volume:

docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18

-v pgdata:/var/lib/postgresql 的意思是:把名為 pgdata 的 Volume 掛到容器內的 /var/lib/postgresql。Volume 不存在時,Docker 會自動建立。

建立一張表、寫入一筆資料,然後刪除容器,再用同一個 Volume 啟動一個全新的容器:

$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18
119ed37ba5c6dc12a773a32d4ef296523529552f94a2ab1ee56113c1b990d7ae

$ docker ps --filter name=db
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS         PORTS      NAMES
119ed37ba5c6   postgres:18   "docker-entrypoint.s…"   8 seconds ago   Up 8 seconds   5432/tcp   db

$ docker exec db psql -U postgres -c "CREATE TABLE notes (id serial, body text); INSERT INTO notes (body) VALUES ('hello volume');"
CREATE TABLE
INSERT 0 1

$ docker exec db psql -U postgres -c "SELECT * FROM notes;"
 id |     body
----+--------------
  1 | hello volume
(1 row)


$ docker rm -f db
db

$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql postgres:18
d94fb2966b3dc5790239b4a48d180aedd193f6fc2c846d6878a7c5fcc450f7cd

$ docker exec db psql -U postgres -c "SELECT * FROM notes;"
 id |     body
----+--------------
  1 | hello volume
(1 row)

新容器讀到了舊容器寫入的資料:資料存在 Volume 裡,和容器的生命週期無關。

PostgreSQL 18 的資料目錄

很多舊教學寫的是 -v pgdata:/var/lib/postgresql/data。這是 PostgreSQL 17 以前的寫法,在 postgres:18 會直接啟動失敗:

$ docker run -d --name db -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:18
c1cec1074a4d440c1f6d106d994171d290cd8ea9da3266d798c0c81662bd8281

$ docker ps -a --filter name=db
CONTAINER ID   IMAGE         COMMAND                  CREATED         STATUS                     PORTS     NAMES
c1cec1074a4d   postgres:18   "docker-entrypoint.s…"   8 seconds ago   Exited (1) 7 seconds ago             db

$ docker logs db
Error: in 18+, these Docker images are configured to store database data in a
       format which is compatible with "pg_ctlcluster" (specifically, using
       major-version-specific directory names).  This better reflects how
       PostgreSQL itself works, and how upgrades are to be performed.

       See also https://github.com/docker-library/postgres/pull/1259

       Counter to that, there appears to be PostgreSQL data in:
         /var/lib/postgresql/data (unused mount/volume)

       This is usually the result of upgrading the Docker image without
       upgrading the underlying database using "pg_upgrade" (which requires both
       versions).

       The suggested container configuration for 18+ is to place a single mount
       at /var/lib/postgresql which will then place PostgreSQL data in a
       subdirectory, allowing usage of "pg_upgrade --link" without mount point
       boundary issues.

       See https://github.com/docker-library/postgres/issues/37 for a (long)
       discussion around this process, and suggestions for how to do so.

$ docker rm -f db
db

$ docker volume rm pgdata
pgdata

PostgreSQL 18 的官方映像檔改把資料放在 /var/lib/postgresql/18/docker 這種「依主要版本分開」的子目錄,以便日後用 pg_upgrade 升級。新的做法是掛載上一層的 /var/lib/postgresql:

$ docker volume ls
DRIVER    VOLUME NAME
local     5775c1d90defc3dc23a92cdde10cd62b182d9c774ae43f0a40595fe62131a63c
local     pgdata

$ docker volume inspect pgdata
[
    {
        "CreatedAt": "2026-10-13T00:09:44Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/pgdata/_data",
        "Name": "pgdata",
        "Options": null,
        "Scope": "local"
    }
]

$ docker exec db sh -c 'echo $PGDATA; ls /var/lib/postgresql'
/var/lib/postgresql/18/docker
18
  • PGDATA 現在是 /var/lib/postgresql/18/docker。
  • docker volume inspect 中的 Mountpoint 是 Volume 在 Docker 主機上的實際位置。在 Docker Desktop 上,它位於 Docker 的 Linux 虛擬機裡,無法直接從 Windows / macOS 的檔案總管看到,所以備份要用下面介紹的方法。

管理 Volume

docker volume ls                 # 列出所有 Volume
docker volume inspect pgdata     # 查看詳細資訊
docker volume rm pgdata          # 刪除(沒有容器使用時才能刪)
docker volume prune              # 刪除未被使用的匿名 Volume
docker volume prune -a           # 連具名 Volume 也一起刪(小心!)

docker volume prune -a 和 docker system prune --volumes 會刪掉資料庫資料。執行前請先確認,或先做備份。

匿名 Volume 從哪裡來

沒有用 -v 指定時,很多資料庫映像檔仍然會建立 Volume,因為它們的 Dockerfile 裡有 VOLUME 指令:

$ docker volume ls
DRIVER    VOLUME NAME

$ docker run -d --name anon -e POSTGRES_PASSWORD=secret postgres:18
1db583d1b34d0482cfa458a5fe4a7fc7c1320633bde0339407a0c24fe62ad630

$ docker volume ls
DRIVER    VOLUME NAME
local     0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8

$ docker inspect --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Destination}}{{println}}{{end}}' anon
volume 0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8 -> /var/lib/postgresql

$ docker rm -f anon
anon

$ docker volume ls
DRIVER    VOLUME NAME
local     0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8

$ docker volume prune -f
Deleted Volumes:
0bbe1b19b9c02afcc2619b031ceddb1af1b4b75c3068f28eb64642973b3fd6e8

Total reclaimed space: 40.49MB
  • 只執行了 docker run,docker volume ls 就多了一個名稱是一長串亂碼的匿名 Volume,掛在 /var/lib/postgresql。
  • docker rm -f 刪除容器後,匿名 Volume 還留著,佔著 40 MB。很多人的 Docker 越用越大,原因之一就是這些被遺忘的匿名 Volume。
  • docker volume prune 會清掉沒有被使用的匿名 Volume。刪除容器時加上 -v(docker rm -fv anon)也會一併刪除它的匿名 Volume。

建議總是使用具名 Volume,一看就知道是做什麼用的。

Bind mount:把主機的資料夾掛進容器

docker run -d --name site -p 8080:80 -v "$(pwd)/site:/usr/share/nginx/html:ro" nginx:1.30
docker run -d --name site -p 8080:80 -v "${PWD}/site:/usr/share/nginx/html:ro" nginx:1.30
docker run -d --name site -p 8080:80 -v "%cd%/site:/usr/share/nginx/html:ro" nginx:1.30
修改前的網頁
nginx 顯示主機上 site/index.html 的內容

在主機上用編輯器修改 site/index.html 的標題並存檔,重新整理瀏覽器:

修改後的網頁
不用重建映像檔、不用重啟容器,重新整理就看到修改結果

這就是開發時最常見的用法:程式碼留在主機上用習慣的編輯器修改,容器負責執行。

注意事項:

  • 主機路徑要寫絕對路徑。$(pwd) 會換成目前目錄(Windows PowerShell 用 ${PWD})。
  • 如果寫的是名稱而不是路徑(例如 site:/data),Docker 會當成 Volume 名稱。
  • 在 Linux 上,容器以 root 寫入的檔案,在主機上也屬於 root。可以用 -u $(id -u):$(id -g) 讓容器以你的身分執行。
  • 唯讀掛載加上 :ro。

-v 與 --mount 遇到不存在的路徑

$ ls -d nothere
ls: nothere: No such file or directory

$ docker run --rm -v "$(pwd)/nothere:/data" alpine:3.23 ls -la /data
total 8
drwxr-xr-x    2 root     root          4096 Oct 13 00:09 .
drwxr-xr-x    1 root     root          4096 Oct 13 00:09 ..

$ ls -ld nothere
drwxr-xr-x    2 root     root          4096 Oct 13 00:09 nothere

$ docker run --rm --mount type=bind,src="$(pwd)/nothere2",dst=/data alpine:3.23 ls /data
docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /work/nothere2

Run 'docker run --help' for more information

對應 Windows 的寫法(點選分頁切換):

docker run --rm -v "$(pwd)/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="$(pwd)/nothere2",dst=/data alpine:3.23 ls /data
docker run --rm -v "${PWD}/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="${PWD}/nothere2",dst=/data alpine:3.23 ls /data
docker run --rm -v "%cd%/nothere:/data" alpine:3.23 ls -la /data
docker run --rm --mount type=bind,src="%cd%/nothere2",dst=/data alpine:3.23 ls /data
  • -v 遇到不存在的主機路徑,會自動建立一個空資料夾(而且屬於 root)。容器啟動成功,但掛進去的是空的,很容易讓人以為「明明掛了卻沒有檔案」。
  • --mount type=bind 遇到不存在的路徑會直接報錯,比較容易發現打錯路徑。

--mount 寫法比較長,但更明確:

docker run -d \
  --mount type=volume,src=pgdata,dst=/var/lib/postgresql \
  --mount type=bind,src="$(pwd)/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly \
  -e POSTGRES_PASSWORD=secret postgres:18
docker run -d `
  --mount type=volume,src=pgdata,dst=/var/lib/postgresql `
  --mount type=bind,src="${PWD}/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly `
  -e POSTGRES_PASSWORD=secret postgres:18
docker run -d ^
  --mount type=volume,src=pgdata,dst=/var/lib/postgresql ^
  --mount type=bind,src="%cd%/init.sql",dst=/docker-entrypoint-initdb.d/init.sql,readonly ^
  -e POSTGRES_PASSWORD=secret postgres:18

tmpfs:存在記憶體裡

$ docker run --rm --tmpfs /cache:size=64m alpine:3.23 df -h /cache
Filesystem                Size      Used Available Use% Mounted on
tmpfs                    64.0M         0     64.0M   0% /cache

資料只存在記憶體中,容器停止就消失,適合暫存檔案(僅限 Linux 容器)。

備份與還原 Volume

Volume 的位置由 Docker 管理,最通用的備份方式是啟動一個臨時容器,把 Volume 打包成 tar 檔:

$ docker run --rm -v pgdata:/data:ro -v "$(pwd):/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .

$ ls -lh pgdata.tar.gz
-rw-r--r--    1 root     root        4.6M Oct 13 00:10 pgdata.tar.gz

$ docker volume create pgdata-restore
pgdata-restore

$ docker run --rm -v pgdata-restore:/data -v "$(pwd):/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data

$ docker run --rm -v pgdata-restore:/data alpine:3.23 ls /data/18/docker
PG_VERSION
base
global
pg_commit_ts
pg_dynshmem
…(以下省略)

$ rm pgdata.tar.gz

對應 Windows 的寫法(點選分頁切換):

docker run --rm -v pgdata:/data:ro -v "$(pwd):/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "$(pwd):/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
docker run --rm -v pgdata:/data:ro -v "${PWD}:/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "${PWD}:/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
docker run --rm -v pgdata:/data:ro -v "%cd%:/backup" alpine:3.23 tar czf /backup/pgdata.tar.gz -C /data .
docker run --rm -v pgdata-restore:/data -v "%cd%:/backup" alpine:3.23 tar xzf /backup/pgdata.tar.gz -C /data
  • 第一個指令:把 pgdata 以唯讀方式掛在 /data,目前資料夾掛在 /backup,用 tar 把 /data 打包成 pgdata.tar.gz,存到主機的目前資料夾。
  • 還原時建立一個新的 Volume,用同樣的方式解壓縮進去。還原後的 Volume 裡可以看到 18/docker 底下的 PostgreSQL 資料檔。

直接打包執行中資料庫的檔案,可能得到不一致的資料。上面的例子是在資料庫停止後備份。資料庫建議使用它自己的匯出工具,例如 pg_dump 或 mysqldump,詳見 用 Docker 跑 PostgreSQL。

該用哪一種?

  • 資料庫、需要長期保存的資料 → Volume
  • 開發時想即時看到程式碼修改 → Bind mount
  • 把主機的設定檔交給容器讀取 → Bind mount(唯讀)
  • 暫存、不需要保存 → tmpfs 或直接寫在容器內

重點回顧

  • 資料庫等需要保存的資料用具名 Volume。
  • 開發時用 Bind mount 讓容器直接使用主機上的程式碼。
  • -v 遇到不存在的主機路徑會建立空資料夾;--mount 會直接報錯。
  • 備份 Volume:用臨時容器同時掛載 Volume 與主機資料夾,用 tar 打包。

動手練習

練習 1:建立 Volume notes,用一個容器寫入檔案,刪除容器後用另一個容器讀出來。

答案
docker volume create notes
docker run --rm -v notes:/data alpine:3.23 sh -c "echo hello > /data/a.txt"
docker run --rm -v notes:/data alpine:3.23 cat /data/a.txt

輸出 hello。Volume 的管理指令見 docker volume。

練習 2:docker run -v data:/app 和 docker run -v ./data:/app 有什麼不同?

答案

data 不含斜線,被當成 Volume 名稱;./data 是路徑,是 Bind mount,掛載主機目前資料夾下的 data。

常見問題

Volume 的資料存在電腦的哪裡?

Linux 上在 /var/lib/docker/volumes/名稱/_data。Docker Desktop 的 Volume 在它的 Linux 虛擬機中,Windows 檔案總管或 Finder 看不到,可以用 Docker Desktop 的 Volumes 畫面查看。

Windows 上 -v 的主機路徑怎麼寫?

PowerShell 用 "${PWD}/site:/app",CMD 用 "%cd%/site:/app",本章的指令都附有三種寫法的分頁。

Bind mount 的檔案權限不對?

Linux 上容器以 root 寫入的檔案,在主機上也屬於 root。用 -u $(id -u):$(id -g) 讓容器以你的身分執行。

更新於 2026年10月14日