# 刪了等於沒刪
事情的開頭很單純:C 槽紅字,Docker Desktop 裡躺著一堆幾個月前的 image,刪掉它們。
刪完,打開檔案總管看 C 槽 —— 數字沒動。
第一反應是不是還沒重新整理,重開機再看,還是沒動。這時候就知道不是操作問題,是我對 Docker Desktop 在 Windows 上怎麼存東西的理解有問題。
先用 docker system df 把帳攤開來看:
docker system df |
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 1 1 186.9MB 0B (0%)
Containers 1 0 16.38kB 16.38kB (100%)
Local Volumes 14 0 143.4MB 143.4MB (100%)
Build Cache 286 0 22.25GB 22.25GB
Image 真的只剩 186.9MB,確實刪乾淨了。但最後一行 —— Build Cache 22.25GB。
問題已經浮出一半了。
# 空間到底藏在哪
# 第一層:image 只是冰山一角
平常講「Docker 佔空間」,直覺會想到 image。但 Docker 實際上至少佔了四種空間:
- Images:拉下來或 build 出來的映像檔
- Containers:容器的可寫層
- Local Volumes:資料卷,容器刪了它也不會跟著走
- Build Cache:BuildKit 的建置快取
docker image prune 、或在 Docker Desktop GUI 裡按刪除,動到的只有第一項。BuildKit 的 build cache 是完全獨立的一塊,每次 docker build 的每個 layer 都會留一份快取下來加速下次建置,長年累積下來很容易比 image 本身大上一個數量級 —— 我這次就是 186.9MB 的 image 對上 22.25GB 的 cache。
所以第一個結論:要清的是 build cache,不是 image。
# 第二層:vhdx 只會長大,不會自己變小
清掉 build cache 之後還有第二關,而且這關才是「數字沒動」的真正元凶。
Docker Desktop 在 Windows 上跑在 WSL2 裡,而 WSL2 的檔案系統不是直接放在 NTFS 上,是包在一個 ** 虛擬硬碟檔(.vhdx)** 裡面。以我的機器為例:
Get-ChildItem "$env:LOCALAPPDATA\Docker\wsl" -Recurse -Filter *.vhdx | | |
Select-Object FullName, @{n='GB';e={[math]::Round($_.Length/1GB,2)}} |
FullName GB
-------- --
C:\Users\<你的帳號>\AppData\Local\Docker\wsl\disk\docker_data.vhdx 60.17
C:\Users\<你的帳號>\AppData\Local\Docker\wsl\main\ext4.vhdx 0.09
docker_data.vhdx 一個檔案就 60.17GB,這才是 C 槽真正被吃掉的那塊。
而 vhdx 是 ** 動態擴充(dynamically expanding)** 格式,它的行為是:
- 裡面資料變多 → 檔案跟著變大
- 裡面資料變少 → 檔案大小不變
你在 Docker 裡刪東西,只是把這顆虛擬硬碟「內部」的空間標記成可用,Windows 看到的還是同一個 60GB 的檔案。這塊被騰出來的空間會留給 Docker 之後自己用,但不會還給 C 槽。
想把它還回去,只能手動做一次 compact(壓縮),把虛擬硬碟裡的空洞剔掉、重新收斂檔案大小。
兩層加起來就是整件事的全貌:
刪 image → 只動到冰山一角
就算清掉 build cache → 空間也只是回到 vhdx 內部
要 compact vhdx,C 槽的數字才會變。
# 實際怎麼清
# Step 1:清掉 build cache
docker builder prune -af | |
docker volume prune -f | |
docker system df |
-a 是連還在用的快取也清, -f 跳過確認。
這步會跑滿久,我的 22GB 跑了幾分鐘,中途 daemon 還被自己打掛過一次:
ERROR: rpc error: code = Unavailable desc = error reading from server: EOF
failed to connect to the docker API at npipe:////./pipe/dockerDesktopLinuxEngine
不用緊張,已經刪掉的部分不會回來。重開 Docker Desktop,再跑一次同樣的指令,它會接著清剩下的。我這次重跑一輪就清完了。
跑到 Build Cache 那行變成 0B 就算完成:
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 1 1 186.9MB 0B (0%)
Containers 1 1 16.38kB 0B (0%)
Local Volumes 8 0 94.46MB 94.46MB (100%)
Build Cache 0 0 0B 0B
# Step 2:完全關掉 Docker 與 WSL
compact 的前提是沒有任何人掛載這顆 vhdx,否則會失敗。
- Docker Desktop 右下角托盤圖示 → Quit Docker Desktop(要完全退出,關掉視窗不算)
- 執行:
wsl --shutdown |
- 確認狀態:
wsl -l -v |
docker-desktop 要顯示 Stopped 才能往下走。
# Step 3:用 diskpart 壓縮 vhdx
開一個系統管理員權限的 PowerShell(Win + X → 終端機 (系統管理員)),貼進去:
@" | |
select vdisk file="C:\Users\<你的帳號>\AppData\Local\Docker\wsl\disk\docker_data.vhdx" | |
attach vdisk readonly | |
compact vdisk | |
detach vdisk | |
"@ | diskpart |
四個動作分別是:指定檔案 → 以唯讀掛載 → 壓縮 → 卸載。用 readonly 掛載是為了讓 diskpart 能安全讀取內容又不會寫入。
進度會停在 100 percent completed 之前不動好幾分鐘,這是正常的,不要中斷。看到 DiskPart 已成功壓縮虛擬磁碟檔案 就完成了。
compact vdisk 一定要系統管理員權限,一般 PowerShell 會直接失敗。
# 結果比對
| 項目 | 處理前 | 處理後 |
|---|---|---|
| Build Cache | 22.25 GB | 0 B |
| docker_data.vhdx | 60.17 GB | 3.45 GB |
| C 槽剩餘空間 | — | 250.93 GB / 464.83 GB |
vhdx 從 60.17GB 掉到 3.45GB,實際還給 C 槽 56.72GB。
值得注意的是,光做 Step 1 清掉的 build cache 是 22GB,但最後 compact 出來的是 56GB —— 中間那 34GB 是過去累積、早就在 Docker 內部被釋放掉、卻一直沒還給 Windows 的舊空間。這也側面說明了 vhdx 只長不縮這件事拖越久、囤得越多。
# 小結
整件事濃縮成三句話:
- 刪 image 幾乎沒用,
docker system df先看清楚空間到底在哪一欄 - 大宗通常是 build cache,用
docker builder prune -af清 - 清完還要關掉 Docker +
wsl --shutdown,再用 diskpartcompact vdisk,C 槽的數字才會真的動