docker prune は、使われなくなったコンテナ・イメージ・ボリューム・ネットワーク・ビルドキャッシュをまとめて削除し、ディスク容量を回復するためのコマンド群です。ディスクが逼迫したサーバーで真っ先に打つ定番の対処であり、docker system prune -a の一撃で数十GBが戻ることも珍しくありません。
ただし、この記事でいちばん伝えたいのは別のことです。削除前に見る docker system df の「RECLAIMABLE(回収可能)」の数字は、実際に空く容量と一致しません。隔離したDocker環境で計測したところ、RECLAIMABLE が 57.15MB と表示されている状態で docker image prune を実行しても、回収されたのは 0B。ディスクの実使用量も 115MiB のまま1バイトも動きませんでした。
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 2 105.4MB 57.15MB (54%) ← 57MB回収できるはず
$ docker image prune -f
Deleted Images:
deleted: sha256:0089332e5cd402879545d83403ceba3c645fa64f62f5a2b7a9d021505ed6be01
Total reclaimed space: 0B ← 実際は 0B
「pruneしたのに容量が減らない」という詰まり方は、ここから生まれます。この記事では6つのprune系コマンドを1つずつ実行し、そのたびに docker system df の表示と du で測った実ディスク使用量の両方を記録して、どのコマンドが何バイト空けたのかを突き合わせました。検証はすべてUbuntu上の隔離したDockerデーモン(Docker Engine 28.5.2 / Docker CLI 29.6.2 / buildx v0.35.0)で行い、出力はそのまま貼っています。
docker pruneとは|使わなくなったDockerリソースをまとめて消す仕組み
Dockerはコンテナを起動・停止・ビルドするたびに、イメージのレイヤーやビルドの中間キャッシュをディスクに書き込みます。そしてそれらは自動では消えません。開発を続けているうちに /var/lib/docker だけで数十GBに膨らみ、ある日 no space left on device で止まる、というのがよくある流れです。
この溜まった不要物を掃除するのが prune(=剪定する)系のコマンドです。
「docker prune」という単体のコマンドは存在しない
最初に用語を整理します。docker prune とだけ打っても動きません。実際には、対象ごとに分かれた6つのサブコマンドの総称です。
| コマンド | 削除される対象 | 危険度 |
|---|---|---|
docker container prune | 停止中のコンテナすべて | 低 |
docker image prune | タグの無いイメージ(ダングリング)のみ | 低 |
docker image prune -a | コンテナに使われていないイメージすべて | 中 |
docker network prune | どのコンテナも使っていないネットワーク | 低 |
docker builder prune | ビルドキャッシュ | 低(再ビルドが遅くなるだけ) |
docker volume prune | 匿名ボリュームのうち未使用のもの | 高(データが消える) |
docker system prune | 上記をまとめて(ボリュームは除く) | 中〜高 |
ポイントは3つです。①イメージは -a の有無で削除範囲が大きく変わる。②ボリュームは docker system prune では消えない。③ビルドキャッシュが実は容量の主犯であることが多い。以降、この3点を実測で確かめていきます。
検証に使った環境
prune は取り消せない操作です。稼働中のサーバーで試すわけにはいかないため、Docker in Docker(docker:28-dind)で完全に隔離したDockerデーモンを立て、その中だけで検証しました。同じ手順は手元でも再現できます。
# 隔離したDockerデーモンを立てる(ホスト側のイメージ・ボリュームには一切触れない)
docker run -d --privileged --name prune-lab -e DOCKER_TLS_CERTDIR= docker:28-dind
# 以降、この中で docker コマンドを叩く
docker exec prune-lab docker system df
検証用に、実際のサーバーでありがちな状態を作りました。タグ付きイメージ3つ、同じタグで再ビルドして生まれたダングリングイメージ1つ、稼働中コンテナ1つ、停止中コンテナ1つ、匿名ボリューム2つ、名前付きボリューム1つ、40MBのレイヤーを含むビルドキャッシュです。
docker system dfのRECLAIMABLEは「実際に空く容量」ではない
掃除の前に現状を見るコマンドが docker system df です。多くの解説記事はここで表示される RECLAIMABLE を「これだけ空く」と説明しますが、この数字は削除される候補のサイズを足し上げただけで、レイヤーの共有を考慮していません。
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 5 2 105.4MB 57.15MB (54%)
Containers 2 1 1.095kB 1.095kB (100%)
Local Volumes 3 1 0B 0B
Build Cache 4 0 86B 86B
この状態から docker image prune(ダングリングのみ削除)を実行した結果が冒頭の出力です。イメージは確かに1つ消えましたが、回収容量は 0B。du で測った実使用量も 115MiB のまま変わりません。
# image prune 実行後
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 4 2 105.4MB 57.15MB (54%) ← 減っていない
overlay2 実使用量: 115MiB ← 変わらない
理由は、消えたダングリングイメージが持っていたレイヤーを、同名で再ビルドした demo:latest がそのまま共有していたからです。イメージという「参照」は消えても、実体のレイヤーは他から参照され続けているため、ディスクは1バイトも解放されません。
本当に空く量は system df -v のUNIQUE SIZEで見る
-v を付けると、イメージ1つずつについて SHARED SIZE(他と共有しているサイズ)と UNIQUE SIZE(そのイメージ固有のサイズ)が出ます。削除して実際に空くのは UNIQUE SIZE のほうです。
$ docker system df -v
Images space usage:
REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNIQUE SIZE CONTAINERS
alpine 3.20 bf8527eb54c3 3 months ago 7.81MB 0B 7.808MB 0
nginx 1.27-alpine 6769dc3a703c 15 months ago 48.2MB 0B 48.24MB 0
CONTAINERS 列が 0 かつ UNIQUE SIZE が大きいイメージが、削除の効果が最も高い候補です。RECLAIMABLE の合計値を見るより、この一覧を見たほうが判断を誤りません。
実測:6コマンドを順に実行してディスクがどう減ったか
同じ環境で prune を段階的に実行し、各段階で docker system df の表示と du -sm /var/lib/docker/overlay2 の実測値を記録しました。結果が次の表です。
| 実行したコマンド | Dockerの報告 | overlay2 実使用量 | 実際の減少 |
|---|---|---|---|
| (初期状態) | ― | 115MiB | ― |
docker image prune | 0B | 115MiB | 0 |
docker image prune -a | 7.398MB | 107MiB | 約8MiB |
docker builder prune -a | 41.94MB | 67MiB | 約40MiB |
docker system prune -a | 48.24MB | 17MiB | 約50MiB |
読み取れることは明確です。イメージ削除系(1行目・2行目)で減ったのは合計8MiBに過ぎず、いちばん効いたのはビルドキャッシュの削除でした。
docker system dfはビルドキャッシュを過小に表示する
ここに、もうひとつの落とし穴があります。初期状態で docker system df は Build Cache を 86B と表示していました。ところが docker image prune -a を実行した直後、同じビルドキャッシュの表示は 41.94MB に跳ね上がります。
# 初期状態
Build Cache 4 0 86B 86B
# docker image prune -a を実行した直後(キャッシュ自体は何もしていない)
Build Cache 4 0 41.94MB 41.94MB
キャッシュの中身は一切変わっていません。イメージが存在するあいだ、共有されているレイヤーは Images 側に計上され、Build Cache 欄からは差し引かれて表示されるためです。つまり掃除を始める前に Build Cache 欄を見ても、実際に溜まっている量はわかりません。
ビルドキャッシュの実量を知りたいときは、専用のコマンドを使います。
$ docker buildx du
ID RECLAIMABLE SIZE LAST ACCESSED
ynlexgj90gv4f204b8ccm565m true 41.94MB 11 seconds ago
975peq165ml6we6yb9l44bvf8* true 86B 11 seconds ago
Reclaimable: 41.94MB
Total: 41.94MB
CI/CDサーバーやローカルの開発機で「イメージを消したのに全然減らない」場合、犯人はほぼこのビルドキャッシュです。docker builder prune -a を実行して初めて、実使用量が 107MiB → 67MiB へ落ちました。
image prune -aとsystem prune -aの決定的な違い
どちらも「使っていないイメージを全部消す」と説明されますが、停止中のコンテナがあると結果が変わります。これは事故に直結する差なので、実測で確認しました。
検証環境には、停止中のコンテナ stopped-web(nginx:1.27-alpine を使用)が置いてあります。この状態で docker image prune -a を実行しました。
$ docker image prune -a -f
untagged: alpine:3.19
untagged: demo:latest
Total reclaimed space: 7.398MB
$ docker images --format '{{.Repository}}:{{.Tag}}'
alpine:3.20
nginx:1.27-alpine ← 停止中コンテナが使っているので残った
docker image prune -a は、停止中であってもコンテナが紐づいているイメージには手を出しません。コンテナが「盾」になります。
ところが docker system prune -a は違います。
$ docker system prune -a -f
Deleted Containers:
06269e2ea1bfc8d92228be5decc715564cff32c45ab9447c538f1503ea56175d
Deleted Images:
untagged: nginx:1.27-alpine ← 消えた
deleted: sha256:6769dc3a703c719c1d2756bda113659be28ae16cf0da58dd5fd823d6b9a050ea
(以下略)
Total reclaimed space: 48.24MB
理由は実行順です。docker system prune -a は最初に停止中コンテナを削除し、そのあとでイメージを削除します。盾になっていたコンテナが先に消えるため、そのイメージも道連れになります。
「今は止めているが、あとで docker start で再開するつもりのコンテナ」がある環境で docker system prune -a を打つと、コンテナごと消えて起動できなくなります。停止中コンテナを残したまま掃除したいなら、docker image prune -a を使ってください。
ボリュームはprune系では簡単に消えない|匿名と名前付きの違い
データベースのデータなど、消えては困るものはボリュームに入っています。ここが最も誤解の多いところなので、4パターンすべて実測しました。
まず前提として、ボリュームには「匿名ボリューム」と「名前付きボリューム」があります。docker run -v /data のようにパスだけ指定すると、Dockerがランダムな64桁のIDを付けて作る匿名ボリュームになります。docker volume create mydata や -v mydata:/data で作るのが名前付きボリュームです。
| コマンド | 未使用の匿名ボリューム | 未使用の名前付きボリューム |
|---|---|---|
docker system prune | 消えない | 消えない |
docker system prune --volumes | 消える | 消えない |
docker system prune -a --volumes | 消える | 消えない |
docker volume prune | 消える | 消えない |
docker volume prune -a | 消える | 消える |
実測の様子です。匿名ボリューム1つと名前付きボリューム named-keep を置いた状態で試しました。
$ docker volume prune -f
Deleted Volumes:
dc8680c19245bde0061f2591efcaa7d79715cc99674dd8bc89b568b072c24f1e
$ docker volume ls --format '{{.Name}}'
a9829a5430bf87b7fbacf6ac56d2fa72d915c273ab99fbf46c9134268c12e649
named-keep ← 名前付きは残る
$ docker volume prune -a -f
Deleted Volumes:
named-keep ← -a で初めて消える
この仕様は確認プロンプトの文面にも明記されています。-f を付けずに実行すると、Docker自身が対象範囲を宣言します。
$ docker volume prune
WARNING! This will remove anonymous local volumes not used by at least one container.
^^^^^^^^^ 匿名のみ
$ docker volume prune -a
WARNING! This will remove all local volumes not used by at least one container.
^^^ すべて
$ docker system prune -a --volumes
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all anonymous volumes not used by at least one container ← ここも匿名のみ
- all images without at least one container associated to them
- all build cache
つまり --volumes を付けても、名前付きボリュームは消えません。MySQLやPostgreSQLのデータを -v dbdata:/var/lib/mysql のように名前付きで持たせていれば、docker system prune -a --volumes を打っても生き残ります。逆に、名前を付けずに運用していたデータは --volumes ひとつで消えます。DockerでMySQLを構築するときに名前付きボリュームを推奨するのは、この差があるからです。
なお古い解説では「--volumes は全ボリュームを消す危険なオプション」と書かれていることがありますが、Docker 23.0以降は上記のとおり匿名のみが対象です。バージョンによって挙動が違う部分なので、docker version を確認してください。
–dry-runは存在しない|削除前に対象を確認する方法
「まず --dry-run で確認してから」と書かれた解説を見かけますが、prune系コマンドに --dry-run はありません。実際に打つとフラグ自体が拒否されます。
$ docker system prune --dry-run
unknown flag: --dry-run
$ docker builder prune --dry-run
unknown flag: --dry-run
$ docker buildx prune --dry-run
unknown flag: --dry-run
代わりに、削除対象を先に一覧するコマンドを使います。prune の各サブコマンドと対になる確認コマンドは次のとおりです。
# container prune が消すもの(停止中・作成済みのコンテナ)
docker ps -a --filter status=exited --filter status=created
# image prune が消すもの(ダングリングイメージ)
docker images -f dangling=true
# image prune -a が消すもの(コンテナに使われていないイメージ)
docker system df -v # CONTAINERS 列が 0 の行
# network prune が消すもの
docker network ls
# builder prune が消すもの
docker buildx du
ボリュームの事前確認だけは結果がズレる
よく紹介される docker volume ls -f dangling=true は、prune が実際に消す範囲より多く表示します。未使用の匿名ボリューム1つと名前付きボリューム1つを置いて確かめました。
$ docker volume ls -f dangling=true --format '{{.Name}}'
681f5375d609d38c67dc9474060d7dd6f0d7db51a690f9414999518a18f04764
named-unused ← 2件表示される
$ docker volume prune -f
Deleted Volumes:
681f5375d609d38c67dc9474060d7dd6f0d7db51a690f9414999518a18f04764
$ docker volume ls --format '{{.Name}}'
named-unused ← 実際に消えたのは1件だけ
dangling=true は「どのコンテナからも使われていない」という意味であり、匿名か名前付きかを区別しません。事前確認としては「この中の匿名ボリュームだけが消える」と読み替える必要があります。
pruneしても容量が減らない3つの原因
ここまでの実測を踏まえて、「実行したのに df の空きが増えない」ときに疑う順番をまとめます。
原因①:ビルドキャッシュがレイヤーを握っている
最も多い原因です。イメージを削除してもレイヤーの実体はビルドキャッシュ側に残り続けます。前述のとおり docker system df の Build Cache 欄は過小表示されるため、気づきにくいのが厄介です。
# イメージを消しても減らないなら、これを実行する
docker builder prune -a -f
原因②:コンテナのログファイル(system dfには一切現れない)
これは prune では絶対に減らない領域です。稼働中のコンテナが標準出力に書いた内容は /var/lib/docker/containers/<ID>/<ID>-json.log に蓄積されます。20万行を出力するコンテナを動かして計測しました。
$ du -sh /var/lib/docker/containers/*/*-json.log
20.9M /var/lib/docker/containers/4790fe48.../4790fe48...-json.log
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Containers 1 1 0B 0B ← ログは計上されない
$ docker system prune -a -f
Total reclaimed space: 0B
$ du -sh /var/lib/docker/containers/*/*-json.log
20.9M ... ← 1バイトも減らない
20.9MBのログが存在するのに docker system df の Containers は 0B と表示し、docker system prune -a の回収も 0B です。コンテナのログは prune の対象外であり、統計にも出てきません。ディスクを食っているのに prune で減らない場合は、ここを直接 du で確認してください。
恒久対策はログのサイズ上限を設定することです。daemon.json か docker run のオプションで指定します。
# /etc/docker/daemon.json(全コンテナに適用。設定後 systemctl restart docker)
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
# コンテナ単位で指定する場合
docker run --log-opt max-size=10m --log-opt max-file=3 nginx
考え方は通常のログ運用と同じです。ログローテーションの基本設計もあわせて確認しておくと、Docker以外のログ肥大にも対応できます。
原因③:Docker Desktopの仮想ディスクは自動で縮まない
macOS/Windowsの Docker Desktop は、Dockerのデータを仮想ディスクファイル(Docker.raw や ext4.vhdx)の中に持っています。prune でファイルシステム内は空いても、仮想ディスクファイル自体のサイズはすぐには縮みません。Docker Desktop の設定画面から手動で領域を回収するか、それでも戻らない場合は仮想ディスクの再作成が必要です。Linuxサーバー上のDockerではこの現象は起きません。
–filterで削除範囲を絞り込む
全消しが怖い場面では --filter で条件を付けます。実務でよく使うのは経過時間とラベルの2つです。
# 24時間より前に作られたイメージだけを削除
docker image prune -a -f --filter "until=24h"
# 停止して1週間経ったコンテナだけを削除
docker container prune -f --filter "until=168h"
# 特定ラベルの付いたものを除外して削除
docker image prune -a -f --filter "label!=keep=true"
# ビルドキャッシュを10GBまで残して削除
docker builder prune -f --reserved-space 10GB
volume pruneにuntilフィルタは使えない
他のコマンドで使える until を docker volume prune に渡すと、デーモンがエラーを返します。
$ docker volume prune -f --filter "until=24h"
Error response from daemon: invalid filter 'until'
ボリュームで使えるのは label フィルタのみです。--filter "label!=prod" のように、消したくないボリュームへ事前にラベルを付けておく運用が現実的です。
–keep-storageは廃止され–reserved-spaceに変わった
ビルドキャッシュの残量指定として広く紹介されている --keep-storage は、現行バージョンでは非推奨です。実行すると警告が出ます。
$ docker builder prune --keep-storage 1GB -f
Flag --keep-storage has been deprecated, keep-storage flag has been changed to reserved-space
現在は用途別に3つのオプションが用意されています。
--reserved-space:常に残しておくキャッシュ容量(旧--keep-storageの後継)--max-used-space:キャッシュが使ってよい上限容量--min-free-space:削除後に確保したいディスク空き容量
安全にディスク容量を回復する手順
実測結果を踏まえた、危険度の低い順の手順です。上から順に実行し、そのつど df -h で空きを確認してください。
# 0. 現状を把握する(ここを飛ばさない)
df -h /var/lib/docker
docker system df -v
# 1. 停止中コンテナを消す(再起動予定が無いことを確認)
docker ps -a --filter status=exited # 先に一覧で確認
docker container prune -f
# 2. ダングリングイメージを消す(効果は小さいが最も安全)
docker image prune -f
# 3. ビルドキャッシュを消す ← ここが本命
docker buildx du # 実量を確認
docker builder prune -a -f
# 4. それでも足りなければ、未使用イメージを消す
docker image prune -a -f --filter "until=168h"
# 5. コンテナのログを確認する(prune では減らない)
du -sh /var/lib/docker/containers/*/*-json.log
# 6. 結果を確認
df -h /var/lib/docker
ボリュームの削除(手順7以降)は、中身のバックアップを取ってからにしてください。prune で消えたボリュームのデータは復元できません。docker volume ls で残っているボリュームを確認し、必要なものは先に退避します。
# ボリュームの中身を tar で退避してから消す
docker run --rm -v mydata:/from -v "$PWD":/to alpine \
tar czf /to/mydata-backup.tar.gz -C /from .
cronで自動化するときの書き方
開発サーバーやCIランナーでは定期実行が有効です。自動化するなら -a は付けず、必ず --filter until= で新しいものを守ります。
# 毎週日曜4時に、1週間以上使われていないものだけ掃除する
0 4 * * 0 /usr/bin/docker container prune -f --filter "until=168h" >> /var/log/docker-prune.log 2>&1
5 4 * * 0 /usr/bin/docker builder prune -f --reserved-space 10GB >> /var/log/docker-prune.log 2>&1
cron には -f(確認プロンプトの抑止)が必須です。付け忘れると入力待ちで止まります。また docker volume prune を cron に入れるのは避けてください。ディスク容量不足を自動通知する仕組みと組み合わせ、閾値を超えたときだけ手動で対応する運用のほうが安全です。
本番環境で実行するときの注意
- 稼働中のコンテナとそのイメージ・ボリュームは、どのpruneでも消えません。サービス断は起きません
- 危ないのはロールバック用に残しておいた旧バージョンのイメージです。
-aを付けると消えます - 停止中コンテナを再開する予定があるなら
docker system prune -aは使わない - 削除対象が何も無い場合でも終了ステータスは 0 です。スクリプトで成否を判定する用途には使えません
よくある質問
docker pruneで削除したデータは元に戻せますか?
戻せません。イメージは docker pull や再ビルドで取り直せますが、ボリュームの中身は復元できません。ゴミ箱に入るような仕組みもありません。ボリュームを消す前には必ずバックアップを取ってください。
docker system prune -aを実行すると稼働中のサービスは止まりますか?
止まりません。稼働中コンテナと、それが使っているイメージ・ボリューム・ネットワークはすべて保護されます。ただし停止中のコンテナは削除され、そのイメージも一緒に消えます。あとで docker start するつもりのコンテナがある場合は注意してください。
docker system prune –volumesは名前付きボリュームも消しますか?
消しません。Docker 23.0以降、--volumes の対象は匿名ボリュームのみです。確認プロンプトにも「all anonymous volumes」と明記されます。未使用の名前付きボリュームまで消すのは docker volume prune -a だけです。
pruneを実行しても容量が減らないのはなぜですか?
多い順に、①ビルドキャッシュがレイヤーを保持している(docker builder prune -a で解決)、②コンテナのログファイルが肥大している(prune の対象外)、③Docker Desktop の仮想ディスクが縮んでいない、の3つです。実測では、イメージ削除だけでは8MiBしか減らず、ビルドキャッシュを消して初めて40MiB減りました。
docker system dfのRECLAIMABLEの分だけ空きますか?
空きません。RECLAIMABLE は削除候補のサイズを単純に合計した値で、レイヤーの共有を考慮していません。実測では RECLAIMABLE 57.15MB の表示に対し、docker image prune の実回収は 0B でした。実際に空く量は docker system df -v の UNIQUE SIZE を見てください。
–dry-runで事前に確認できますか?
prune系コマンドに --dry-run は実装されていません(unknown flag: --dry-run が返ります)。docker ps -a --filter status=exited、docker images -f dangling=true、docker buildx du など、対象を一覧するコマンドで代替してください。
docker image pruneとdocker image prune -aはどちらを使うべきですか?
まずは -a なしから試してください。-a なしはタグの無いイメージだけが対象なので安全ですが、実測では回収 0B のこともあります。効果が無ければ -a --filter "until=168h" のように期間を切って範囲を広げるのが現実的です。-a を無条件で付けると、ベースイメージも消えて次のビルドが大幅に遅くなります。
docker purgeというコマンドはありますか?
ありません。purge は apt purge などパッケージ管理側の用語です。Dockerで不要リソースを削除するのは prune 系コマンドです。
まとめ
docker prune単体のコマンドは無く、対象別に6つのサブコマンドがあるdocker system dfの RECLAIMABLE は実際に空く容量ではない。57.15MB表示で実回収 0B だった。-vの UNIQUE SIZE を見る- いちばん効くのはビルドキャッシュの削除。イメージ削除で8MiB、
docker builder prune -aで40MiB減った docker image prune -aは停止中コンテナのイメージを守るが、docker system prune -aはコンテナごと消す--volumesを付けても名前付きボリュームは消えない。消えるのはdocker volume prune -aのみ--dry-runは存在せず、--keep-storageは--reserved-spaceに置き換わった- コンテナのログは
docker system dfに 0B と表示され、prune では1バイトも減らない。max-sizeの設定で対処する
迷ったら、docker builder prune -a -f から試すのがおすすめです。データを失うリスクが無く、実測でいちばん容量が戻ったコマンドです。

コメント