Docker prune 清理 VPS 磁碟空間指南
VPS 磁碟已滿但 Docker 占用空間?先用 docker system df 找出 images、containers、build cache 或 volumes,再執行最小範圍 prune,避免誤刪資料。
在執行清理前,先找出哪些項目占用磁碟空間
Docker 會在 VPS 的4個位置占用磁碟空間:images、已停止的 containers、build cache 及 local volumes。先執行 docker system df,找出空間由哪一項占用,再執行能清除該項目的最小範圍 prune。順序很重要,因為本指南最後的指令 docker volume prune -a 會刪除資料,而且無法復原。
先檢查檔案系統,不要先從 Docker 開始。
df -h /
sudo du -xh --max-depth=1 /var/lib/docker | sort -hdf 會顯示磁碟空間使用情況。du 會顯示空間的分布位置。-x 旗標會讓 du 僅檢查單一檔案系統,因此不會進入獨立的 volume mount 並重複計算。這裡有5個重要目錄:overlay2 存放 image 與 container layers,volumes 存放 volume data,containers 存放 container metadata 與 log files,buildkit 存放 build cache,image 存放 layer metadata。
另外說明 sudo 與 shell wildcards,因為這經常造成問題。/var/lib/docker 的擁有者是 root,普通使用者無法讀取,因此 ls /var/lib/docker 會回傳 Permission denied。像 sudo du -sh /var/lib/docker/* 這樣的指令也會失敗,因為 shell 會在 sudo 執行前先展開 *,而 shell 無法讀取該目錄。以下每個指令都使用 find 或 --max-depth,而不是 wildcard,原因正是如此。
接著查看 Docker 自身的資訊。
docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 24 6 12.4GB 8.91GB (71%)
Containers 31 6 1.42GB 1.39GB (97%)
Local Volumes 14 5 6.03GB 4.11GB (68%)
Build Cache 212 0 9.87GB 9.87GB這些數值來自一台機器,無法代表你的環境。請改為觀察其結構。TOTAL 會計算物件數量,ACTIVE 會計算目前正在使用的物件數量,而 RECLAIMABLE 是 Docker 估算該列執行 prune 後可釋出的空間。
RECLAIMABLE 有兩點容易造成誤解。它會針對每個使用該 shared image layer 的 image 各計算一次,因此 image 列通常會高估實際可釋出的空間。此外,它永遠不會包含 container log files,因為 Docker 不會將 log file 視為可回收物件。當 du 顯示的目錄遠大於 docker system df 所列出的大小時,原因通常是 log files;下方會說明這部分。
加入 -v,查看各物件的明細。
docker system df -v這會將摘要依物件類型分成不同區段。image 區段會增加 SHARED SIZE 與 UNIQUE SIZE 欄位,讓你查看單一 image 的實際占用量。volume 區段會增加 LINKS 計數,表示附加至該 volume 的 containers 數量。請記住 LINKS,因為數值為 0 就是 volume prune commands 適用的完整判斷條件。
懸空映像與未使用映像
這兩個詞看似可以互換,但實際上並非如此。由於物件不同,篩選條件的行為也不同。
懸空映像是沒有標籤的映像。在 docker images 中會顯示為 <none>。每次重新建置時都會產生一個懸空映像:docker build -t myapp:latest . 將 myapp:latest 標籤移到新映像,而舊映像保留所有 layer,但失去名稱。沒有任何項目參照它,也不會自行清除。
未使用映像是目前沒有任何容器參照的映像,無論是否有標籤。上個月拉取、目前未執行的 postgres:16 就是未使用映像,但不是懸空映像。
docker image prune # dangling images only
docker image prune -a # every image no container refers to第二個指令會先詢問。
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N]請仔細閱讀該提示。「與它們關聯」是指現有的容器物件,無論容器正在執行或已停止。如果執行了 docker compose down,容器就會被移除,因此這些服務使用的所有映像現在都屬於未使用映像,而 -a 會將它們全部刪除。無法重新取得的資料不會遺失,但下一次執行 docker compose up -d 時,會重新拉取或重新建置全部內容。對小型 VPS 而言,這會耗用頻寬與建置時間。因此,在清理任何映像前,應先了解 docker compose down 會移除什麼,以及 stop 會讓什麼繼續執行。
篩選條件可以排除最近建立的映像。
docker image prune -a --filter "until=240h"這會移除建立時間超過 240 小時(10 天)的未使用映像,較新的映像則會保留。until 的值可使用 240h 這類 Go duration 字串,也可以使用 2026-08-01T00:00:00 這類絕對時間戳記。
Build cache 的作用,以及它為何會無限制成長
自 Docker Engine 23.0 起,BuildKit 是 Docker 預設用於 docker build 與 docker compose build 的建置工具。它會快取所執行每個 Dockerfile 每個步驟的結果,並將快取保存在 /var/lib/docker/buildkit。快取能讓第二次建置在幾秒內完成,因此它確實發揮作用。問題在於,預設不會有任何機制讓舊項目過期。若使用每次都會變更的 COPY 步驟,將同一個映像檔建置 50 次,就會保留 50 組 layer。
docker image prune 看不到 build cache。它是獨立的物件類型,使用自己的 command。
docker builder prune # dangling cache
docker builder prune -a # all unused cache
docker builder prune --filter until=168h # cache untouched for 7 days這些操作都不會影響映像檔或資料。清除 build cache 唯一的代價,是下一次建置只會暫時變慢。在經常重新建置映像檔的 VPS 上,Build Cache 通常是 docker system df 中最大的項目,因此也是最適合刪除的大型項目。
由安全到破壞性的 prune 命令
依序執行以下清單,直到 df -h / 恢復正常為止。每個命令完成時都會輸出一行 Total reclaimed space:。
docker container prune會移除已停止的容器。容器的可寫入層也會一併移除,因此容器在 volume 外寫入的任何內容都會隨之刪除。不會處理 volume。docker image prune只會移除 dangling image。這是最安全的 image 清理命令。docker builder prune會移除 dangling build cache。代價是下一次建置會變慢。docker image prune -a會移除所有沒有任何容器參照的 image。代價是必須重新拉取或重新建置。docker system prune會同時執行前 3 項操作,並加入未使用的 network。docker volume prune會移除未使用的 anonymous volume。docker volume prune -a會移除未使用的 volume,包括 named volume。這個命令會刪除資料庫。
docker system prune 會在執行前說明其作用範圍。
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- unused build cache
Are you sure you want to continue? [y/N]清單刻意未包含 volume。加入 --volumes 後,anonymous volume 也會納入處理範圍。加入 -a 後,image 的處理範圍會從 dangling image 擴大到所有未使用的 image。在 production host 上執行完整的 docker system prune -a --volumes -f,可能會在嘗試釋放空間時遺失資料。
為什麼清理 volume 會刪除資料庫
這一節請讀兩次。
當沒有任何容器附加至 volume 時,該 volume 就會被視為未使用。判定條件只有這一項。Docker 不會檢查 volume 是否為空、compose 檔案是否仍宣告該 volume,或其中是否存放資料庫的唯一副本。LINKS 0 中的 docker system df -v 代表可清理,沒有其他含義。
接著連續執行兩個一般操作。你執行 docker compose down,以乾淨方式重新啟動 stack。這會移除容器,並保留 named volume,這正是文件所述的行為。此時你的 Postgres volume 已不再附加至任何容器。10 分鐘後,你執行 docker volume prune -a 以釋放空間,資料庫就消失了。兩個命令都正確執行,但這個操作順序摧毀了資料。
自 Docker Engine 23.0(API version 1.42)起,直接執行該命令的清理範圍已比過去小。
WARNING! This will remove anonymous local volumes not used by at least one container.anonymous volume 是由 Docker 代為建立的 volume,通常是因為 image 宣告了 VOLUME,而你沒有為它指定名稱。這類 volume 通常存放你未要求保留的資料。named volume,也就是你寫入 compose 檔案的類型,只有在加入 -a 後才會被移除。較舊版本的 Docker 會在直接執行該命令時同時移除這兩類 volume,因此不要相信在已升級主機上養成的舊習慣。理解這項差異前,請先了解named volume 與 bind mount 的差異,因為 bind mount 根本不是 Docker volume,任何 prune 命令都不會觸及它。
刪除前先確認。將 myapp_pgdata 替換為你要檢查的 volume 名稱。
docker volume ls -f dangling=true
docker volume inspect myapp_pgdata
sudo ls -la /var/lib/docker/volumes/myapp_pgdata/_datavolume 上的 dangling=true filter 代表未被參照,不代表內容為空。列出 _data 的內容,可查看其中實際存放的資料。如果發現 pgdata 或 mysql 目錄,請立即停止,並先建立副本。透過 docker compose down -v 也會造成相同的資料遺失;該命令會移除 compose 檔案宣告的所有 volume,且不會先詢問你。
在 Docker host 上,volume 是重建時無法重新建立的唯一項目。因此,volume 資料應備份至在伺服器外部執行的 restic backup,避免錯誤輸入 flag 時連備份資料也受到影響。
什麼都清不掉:容器日誌檔案
您已清除所有項目,docker system df 幾乎顯示沒有可回收的空間,但磁碟仍然已滿。請檢查日誌。
sudo find /var/lib/docker/containers -name '*-json.log' -exec du -h {} + | sort -h | tail -10每個容器都會將標準輸出與標準錯誤寫入 /var/lib/docker/containers/ 下的 JSON 檔案。預設安裝中,max-size 未設定,表示大小不受限制。因此,單一容器若陷入 crash loop,可能持續寫入,直到分割區已滿。沒有任何 prune 指令會移除這些檔案,因為產生這些檔案的容器仍在執行;依定義,這些檔案無法進行 prune。
請勿刪除檔案。對開啟中的日誌檔案執行 rm 不會釋放任何空間,因為 Docker daemon 仍持有開啟的檔案描述元,而 kernel 會持續配置這些區塊,直到該 handle 關閉。df 完全不會移動。請改用 truncate。這會保留相同的 inode,讓 daemon 繼續寫入。
sudo find /var/lib/docker/containers -name '*-json.log' -exec truncate -s 0 {} +
df -h /這只是暫時的修正。現在對這些容器執行 docker logs 不會回傳任何內容,檔案也會立即再次增長。真正的修正方式是設定 rotation,內容請見下一節。
每次都在前後測量
不要猜測 prune 執行了什麼。先測量一次,執行一個命令,再測量一次。
df -h /
docker system df
docker builder prune -f --filter until=168h
docker image prune -f
docker system df
df -h /比較兩次 df 的輸出。只有這個數值能判斷伺服器是否仍可提供服務。docker system df 會進一步指出實際變動的資料列,而每次 prune 都會顯示自己的 Total reclaimed space: 數值。
如果 df 沒有變動,但 docker system df 顯示已釋放空間,表示開啟的檔案控制代碼仍保留已刪除的區塊。這就是上述的 log 檔案問題。如果兩者都發生變動,但磁碟在一天內再次用滿,表示問題在於資料成長,而不是清理失效。解決方式是設定輪替並加入排程工作。
如何避免磁碟再次填滿
限制日誌大小。 建立或編輯 /etc/docker/daemon.json。
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}這會將每個容器的日誌大小限制為 30 MB。log-opts 下的每個值都必須是字串,包括數值。重新啟動前先確認檔案可以正確解析,因為格式錯誤的 daemon.json 會導致 daemon 完全無法啟動,所有容器也會一併停止。
python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -i 'logging driver'現在 docker info 應會回報 Logging Driver: json-file。重新啟動後建立的容器,其限制會出現在 docker inspect 的 LogConfig 區段中。這是重要細節:此設定只適用於新容器。現有容器會保留建立時採用的設定,因此必須重新建立。
docker compose up -d --force-recreate也可以在 compose 檔案中針對單一服務設定相同的限制。當某個高噪音服務需要獨立的數值時,這是較好的做法。
services:
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"排程執行範圍狹窄的清理。 每週只清理 dangling images 與舊的 build cache。排程工作中絕不要加入 -a 或 --volumes,因為 stack 停止時若工作剛好執行,會刪除該 stack 的映像檔;搭配 --volumes 時,還會直接對資料執行操作。
sudo tee /etc/cron.weekly/docker-prune >/dev/null <<'EOF'
#!/bin/sh
docker image prune -f
docker builder prune -f --filter until=168h
EOF
sudo chmod +x /etc/cron.weekly/docker-prune
sudo /etc/cron.weekly/docker-prune最後一行會先手動執行一次 script,讓你在它以 unattended 模式執行前查看輸出。檔案必須具備可執行權限,檔名也不得包含句點,因為 run-parts 會略過不可執行的檔案,以及帶有副檔名的檔案。
針對可用空間設定警示。 磁碟填滿後執行 prune 是復原措施。在使用率達到 80 percent 時發出警示,才是預防措施。
usage=$(df --output=pcent / | tr -dc '0-9')
[ "$usage" -ge 80 ] && echo "root filesystem at ${usage} percent full"將這段內容加入 cron,並使用你現有的 notifier。可用空間只是其中一半,因此請將警示搭配 VPS 上的磁碟健康監控,因為磁碟故障與磁碟填滿都會使容器停止,但兩者需要不同的處理方式。
以上內容都假設採用標準安裝,且 data root 位於 /var/lib/docker。如果你透過 daemon.json 中的 data-root key 移動了 data root,請在每個 command 中替換成你的路徑。在新主機上正確規劃這種配置,是在 VPS 上設定 Docker的一部分;在錯誤的 partition 上累積 40 GB 容器之前先做決定,會容易得多。
FAQ
docker system prune 會刪除我的 volume 嗎?
不會。未加其他選項的命令會移除已停止的容器、未使用的 network、dangling image,以及未使用的 build cache;確認提示也會明確列出這些項目。只有加入 --volumes 後,才會處理 volume;而自 Docker Engine 23.0 起,此旗標涵蓋的是 anonymous volume,不是 named volume。named volume 會由 docker volume prune -a 和 docker compose down -v 移除。這兩個命令需要特別小心。
為什麼執行 docker prune 後,磁碟仍然已滿?
通常有兩個原因。第一個是 /var/lib/docker/containers/ 下的容器 log 檔案。任何 prune 命令都不會處理這些檔案;在設定 max-size 前,它們會持續增長而不受限制。第二個是某個程序仍保持開啟狀態的已刪除檔案:如果容器仍在執行時以 rm 移除了 log,daemon 仍會保留檔案描述元,kernel 也不會釋放這些區塊,因此 df 會顯示沒有變化。比較 sudo du -xh --max-depth=1 /var/lib/docker 與 docker system df,即可確認屬於哪一種情況。
docker image prune 與 docker image prune -a 有什麼差異?
未加其他選項的命令只會移除 dangling image,也就是失去 tag 的 image;這幾乎都是因為重新建置造成的。-a 形式會移除所有沒有任何現有容器參照的 image,包括你刻意拉取的 tagged image。執行 docker compose down 後,容器已經移除,因此 -a 也會一併移除該 stack 的 image。這些內容不會永久遺失,因為下次啟動時會重新拉取或重新建置;但如果網路連線速度很慢,就需要長時間等待。
如何避免 Docker log 填滿磁碟?
在 /etc/docker/daemon.json 的 log-opts 下設定 max-size 和 max-file,然後使用 sudo systemctl restart docker 重新啟動 daemon。這項設定只會套用到該次重新啟動後建立的容器,因此請使用 docker compose up -d --force-recreate 重新建立目前執行中的容器。你也可以在 compose file 中,於 logging key 下為各 service 設定相同的兩個選項。當某個 service 產生的 log 遠多於其他 service 時,應採用這種方式。
在 cron job 中執行 docker system prune 安全嗎?
在所有 stack 都持續執行的主機上,未加其他選項的 docker system prune -f 通常是安全的;但它會移除已停止的容器,因此也會刪除你刻意停止、原本打算稍後重新啟動的容器。較安全的排程工作是 docker image prune -f 加上 docker builder prune -f --filter until=168h。這會釋放增長最快的兩類項目,且不會接觸 volume。切勿排程執行 -a 或 --volumes。