C 槽又滿了,想說 Docker 裡面一堆用不到的 image,刪一刪應該能換回幾十 GB。
結果 image 刪光了,C 槽剩餘空間一點動靜都沒有。

這篇記錄一次把 60GB 壓回 3.45GB 的完整過程,以及為什麼「刪 image」本身幾乎沒用。

# 刪了等於沒刪

事情的開頭很單純: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,否則會失敗。

  1. Docker Desktop 右下角托盤圖示 → Quit Docker Desktop(要完全退出,關掉視窗不算)
  2. 執行:
wsl --shutdown
  1. 確認狀態:
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 只長不縮這件事拖越久、囤得越多。

# 小結

整件事濃縮成三句話:

  1. 刪 image 幾乎沒用, docker system df 先看清楚空間到底在哪一欄
  2. 大宗通常是 build cache,用 docker builder prune -af
  3. 清完還要關掉 Docker + wsl --shutdown ,再用 diskpart compact vdisk ,C 槽的數字才會真的動