Learning Objectives
- 說明容器的可讀寫層:容器刪除後,寫在裡面的資料會一起消失
- 比較 bind mount、volume 與 tmpfs 三種掛載方式,依情境選擇
- 用 -v 或 --mount 把主機目錄掛進容器,讓主機與容器共用檔案
- 用 docker volume create/ls/inspect 管理 volume,讓多個容器共用資料
容器刪掉,資料就不見了
第 03 課說過:容器停止不等於刪除,停止的容器裡檔案還在。可是一旦 docker container rm,容器裡寫入的所有檔案都會跟著消失。
原因在於容器的檔案系統是一層一層疊起來的:
[ 容器的可讀寫層 writable layer ] ← 你在容器裡新增、修改的檔案都寫在這裡;rm 時一起刪除
[ 映像檔的層(唯讀) ]
[ 映像檔的層(唯讀) ] ← 同一個映像檔建立的容器共用這些層
把資料只放在可讀寫層,會遇到三個問題:
| 問題 | 說明 |
|---|---|
| 容器刪除,資料就消失 | 資料庫、上傳的檔案、你寫的筆記本,都會跟著容器一起不見 |
| 很難拿出來給別人用 | 資料和這個容器綁在一起,主機上的其他程式或其他容器不容易讀到 |
| 效能較差 | 可讀寫層由儲存驅動程式(例如 overlay2)透過聯合檔案系統(union filesystem)管理,比直接寫進主機的檔案系統多一層轉換 |
解法是把資料放到容器外面,再「掛載」(mount)進容器。容器只是借用這個位置,容器刪掉了,資料還留著。
三種掛載方式
| 方式 | 資料放在哪裡 | 誰可以修改 | 適合的情境 |
|---|---|---|---|
| bind mount | 主機上你指定的任何目錄,例如 /home/student/notebooks |
主機上的任何程式都能直接改,容器也能改 | 開發時讓主機的編輯器與容器共用原始碼、筆記本 |
| volume | 主機上由 Docker 管理的位置(Linux 上是 /var/lib/docker/volumes/) |
透過 Docker 使用;不建議主機上的其他程式直接去動 | 資料庫的資料、多個容器要共用的資料;Docker 保存資料的建議方式 |
| tmpfs mount | 只放在主機的記憶體,不寫進硬碟 | 只有這個容器 | 暫存、不想留在硬碟上的敏感資料;容器停止就清空 |
主機 容器
/home/student/notebooks ──bind mount──▶ /home/jovyan/work
Docker 管理的 share-data ──volume─────▶ /share
記憶體 ─────────────────tmpfs─────────▶ /tmp/cache
在 Windows 與 macOS 的 Docker Desktop 上,Docker 其實跑在一台小型虛擬機裡,所以 volume 的實際位置在那台虛擬機裡面,不是你在檔案總管看得到的資料夾。用 Docker 指令操作 volume 就不必在意這個差別。
bind mount:主機目錄直接掛進容器
投影片的例子:建立一個 Jupyter 容器,讓筆記本(.ipynb)直接存在主機的目錄裡,容器刪掉也不怕。
mkdir -p ~/notebooks
cd ~/notebooks
docker container run -p 8800:8888 --mount type=bind,source=$PWD,target=/home/jovyan/work --name ds_test jupyter/datascience-notebook
$PWD 是目前所在的目錄(這裡是 ~/notebooks),掛到容器裡 Jupyter 的工作目錄 /home/jovyan/work。
用 docker container logs ds_test 找到帶 token 的網址,在瀏覽器打開(主機的 8800 埠),進入 work 目錄建立筆記本並存檔,
回到主機用 ls ~/notebooks 就看得到那個 .ipynb 檔。
同一件事也可以用比較短的 -v 寫:
docker container run -p 8801:8888 -v $PWD:/home/jovyan/work --name pyspark_test jupyter/pyspark-notebook
| 寫法 | 格式 | 主機目錄不存在時 |
|---|---|---|
-v 主機路徑:容器路徑 |
用冒號分隔,短 | 自動建立一個空目錄(擁有者是 root,常造成權限問題) |
--mount type=bind,source=…,target=… |
每個欄位都寫名稱,長但清楚 | 直接報錯,提醒你路徑打錯了 |
注意事項:
- 冒號左邊是主機,右邊是容器;順序寫反了,就會把容器裡不存在的路徑當成主機路徑。
- 容器裡的程式要有寫入主機目錄的權限。Jupyter 映像檔用一般使用者
jovyan執行,如果存檔時出現 Permission denied,要調整主機目錄的權限(投影片用chmod開放/share的寫入權限)。 - 在容器裡刪除掛載目錄中的檔案,主機上的檔案也會被刪除:它們是同一份檔案,不是複本。
- Jupyter 的映像檔後來改放在 quay.io(例如
quay.io/jupyter/datascience-notebook),Docker Hub 上的jupyter/…版本較舊,觀念相同。
volume:交給 Docker 管理的資料空間
volume 有名字,由 Docker 建立與管理:
| 指令 | 作用 |
|---|---|
docker volume create share-data |
建立名為 share-data 的 volume |
docker volume ls |
列出所有 volume |
docker volume inspect share-data |
查看詳細資訊,例如實際位置 Mountpoint |
docker volume rm share-data |
刪除 volume(還有容器在用時不能刪) |
docker volume prune |
刪除沒有任何容器使用的 volume(請先確定裡面沒有要保留的資料) |
docker volume create share-data
docker volume inspect share-data
[
{
"Driver": "local",
"Mountpoint": "/var/lib/docker/volumes/share-data/_data",
"Name": "share-data",
"Scope": "local"
}
]
建立容器時把 volume 掛進去,兩種寫法效果相同:
docker container run -it --name test1_env -v share-data:/share ubuntu:24.04
docker container run -it --name test1_env --mount 'type=volume,src=share-data,dst=/share' ubuntu:24.04
-v 冒號左邊如果是名字(share-data)就是 volume;如果是路徑(以 / 或 $PWD 開頭)就是 bind mount。
指定的 volume 還不存在時,Docker 會自動建立一個。
多個容器共用同一個 volume:test1_env 在 /share 寫入檔案後,即使把 test1_env 刪掉,再建立 test2_env 掛上同一個 share-data,
/share 裡的檔案還在。在主機上(Linux)也可以用 ls /var/lib/docker/volumes/share-data/_data 看到同一份檔案(需要 root 權限)。
什麼時候用哪一種
| 情境 | 建議 | 理由 |
|---|---|---|
| 開發網站,想用主機上的編輯器改程式,容器立刻讀到新版 | bind mount | 主機與容器看到的是同一個目錄 |
| Jupyter 筆記本要留在自己電腦的資料夾,方便備份或用 Git 管理 | bind mount | 檔案就在你指定的位置 |
| MySQL 的資料庫檔案 | volume | 由 Docker 管理,不怕主機上的程式誤改,搬移與備份有一致的做法 |
| 兩個容器要讀寫同一批資料 | volume | 容器之間共用,與哪個容器活著無關 |
| 暫存檔、不想寫進硬碟的資料 | tmpfs | 只放在記憶體,容器停止就消失 |
預告:Dockerfile 的 VOLUME 指令。 下一課開始寫 Dockerfile。映像檔的作者可以在 Dockerfile 裡寫:
VOLUME ["/share"]
意思是「/share 是放資料的地方,請不要把資料留在可讀寫層」。執行容器時如果你沒有用 -v 指定,
Docker 會自動建立一個匿名 volume(名字是一長串亂碼)掛在那裡。MySQL 的官方映像檔就宣告了 VOLUME /var/lib/mysql,
所以實際使用時建議自己取名掛上:-v mysql-data:/var/lib/mysql,之後才找得到、管得動。
想一想:資料存在哪裡
在容器裡建立 /data.txt(沒有掛載任何東西),然後 exit 並 docker container rm 這個容器。/data.txt 會怎樣?
docker container run -v $PWD:/app ubuntu:24.04 用的是哪一種掛載?
要存放 MySQL 容器的資料庫檔案,最建議用哪一種方式?
想一想:volume 指令
test1_env 掛載 share-data:/share 並寫入 /share/data.txt,之後 test1_env 被刪除。新建的 test2_env 也掛載 share-data:/share,執行 cat /share/data.txt 會?
想知道 volume share-data 在主機上的實際位置,用哪個指令?
重點精華回顧
- 容器裡新增或修改的檔案寫在可讀寫層,
docker container rm之後就一起消失;要保留的資料要放在容器外面再掛載進來。 - 三種掛載:bind mount(主機任意目錄)、volume(Docker 管理,保存資料的建議方式)、tmpfs(只在記憶體)。
- bind mount 用
-v $PWD:/容器路徑或--mount type=bind,source=…,target=…;主機與容器看到的是同一份檔案。 - volume 用
docker volume create/ls/inspect管理,掛載時寫-v share-data:/share或--mount 'type=volume,src=share-data,dst=/share'。 - 多個容器掛載同一個 volume,就能共用資料;容器刪除後,volume 裡的資料還在。
- Dockerfile 的
VOLUME宣告資料存放的位置;沒有指定時 Docker 會建立匿名 volume,實務上建議自己取名掛載。