Docker pruneでVPSのディスク容量を空ける方法
VPSのディスクが満杯なら、Dockerのイメージ、停止済みコンテナ、ビルドキャッシュ、ボリュームの使用量を確認し、データを失わない範囲で安全にpruneする方法を解説します。
プルーニングの前にディスク領域の使用状況を確認する
Docker は VPS 上のディスク領域を、イメージ、停止済みコンテナ、ビルドキャッシュ、ローカルボリュームの 4 か所で使用します。まず 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 を 1 つのファイルシステム内に限定します。そのため、別のボリュームへのマウントをたどって二重に数えることがありません。ここで重要なのは 5 つのディレクトリです。overlay2 にはイメージとコンテナのレイヤーが保存されます。volumes にはボリュームデータが保存されます。containers にはコンテナのメタデータとログファイルが保存されます。buildkit にはビルドキャッシュが保存されます。image にはレイヤーのメタデータが保存されます。
sudo とシェルのワイルドカードについて説明します。ここで多くの時間を無駄にします。/var/lib/docker は root が所有しており、通常のユーザーは読み取れません。そのため、ls /var/lib/docker は Permission denied を返します。sudo du -sh /var/lib/docker/* のようなコマンドも失敗します。シェルが * を展開する時点では sudo はまだ実行されておらず、シェルはそのディレクトリを読み取れないためです。以下のすべてのコマンドで、ワイルドカードの代わりに find または --max-depth を使用しているのは、このためです。
次に、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これらの数値は 1 台のマシンから取得したものであり、お使いの環境については何も示しません。数値の内訳に注目してください。TOTAL はオブジェクト数を数え、ACTIVE は現在使用中のオブジェクト数を数えます。RECLAIMABLE は、その行から prune で解放できる領域についての Docker の見積もりです。
RECLAIMABLE には注意が必要な点が 2 つあります。共有されているイメージレイヤーは、それを使用するイメージごとに 1 回ずつ数えられるため、通常、イメージの行に表示される解放可能領域は実際に得られる領域より多くなります。また、コンテナのログファイルは決して含まれません。Docker はログファイルを回収可能なオブジェクトとして扱わないためです。du で示されるディレクトリが docker system df の示す値より大幅に大きい場合、その原因はログファイルです。これについては後のセクションで説明します。
オブジェクトごとの内訳を表示するには -v を追加します。
docker system df -vこれにより、オブジェクトの種類ごとに 1 つのセクションへ概要が分割されます。イメージのセクションには SHARED SIZE 列と UNIQUE SIZE 列が追加されるため、個々のイメージが実際に使用している領域を確認できます。ボリュームのセクションには LINKS の数が追加されます。これは、そのボリュームに接続されているコンテナ数です。LINKS を覚えておいてください。値が 0 であることが、ボリュームの prune コマンドを適用するための唯一の判定条件です。
タグなしイメージと未使用イメージの違い
この2つの用語は同じ意味に見えますが、実際は異なります。対象となるオブジェクトが異なるため、フィルターの動作も異なります。
タグなしイメージとは、タグが付いていないイメージです。docker images では <none> と表示されます。再ビルドするたびに1つ作成されます。docker build -t myapp:latest . によって myapp:latest タグが新しいイメージへ移され、古いイメージはすべてのレイヤーを保持したまま、名前を失います。どのオブジェクトからも参照されず、自動的に削除されることもありません。
未使用イメージとは、タグの有無にかかわらず、現在どのコンテナからも参照されていないイメージです。先月 pull した 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 ではすべてを再 pull または再ビルドするため、小規模な VPS では帯域幅とビルド時間を消費します。削除を実行する前に、docker compose down で削除され、stop で実行状態のまま残るものを把握しておくべき実際的な理由の1つです。
フィルターを使うと、最近作成されたイメージを対象外にできます。
docker image prune -a --filter "until=240h"これにより、240時間(10日)を超えて前に作成された未使用イメージだけが削除され、新しいイメージは残ります。until の値には、240h のような Go の期間文字列、または 2026-08-01T00:00:00 のような絶対タイムスタンプを指定します。
ビルドキャッシュとは何か、なぜ無制限に増えるのか
BuildKit は、Docker Engine 23.0 以降で docker build と docker compose build に対して Docker がデフォルトで使用するビルダーです。実行するすべての Dockerfile の各ステップの結果をキャッシュし、そのキャッシュを /var/lib/docker/buildkit に保存します。2 回目のビルドが数秒で完了するのは、このキャッシュが機能しているためです。問題は、デフォルトでは古いエントリを期限切れにする仕組みがないことです。毎回内容が変わる COPY ステップを含む同じイメージを 50 回ビルドすると、レイヤーのセットが 50 組蓄積されます。
ビルドキャッシュは docker image prune からは見えません。独自のコマンドを持つ、別のオブジェクト種別です。
docker builder prune # dangling cache
docker builder prune -a # all unused cache
docker builder prune --filter until=168h # cache untouched for 7 daysこれらの操作でイメージやデータが変更されることはありません。ビルドキャッシュを削除する唯一のコストは、次回のビルドが 1 回だけ遅くなることです。定期的にイメージを再ビルドする VPS では、Build Cache が docker system df の中で最大の項目になることがよくあります。そのため、安全に削除できる大容量の対象です。
安全性の高いものから破壊的なものへ並べた prune コマンド
この一覧を上から順に実行し、df -h / が正常な状態に戻ったら、その時点で止めます。各コマンドは完了時に Total reclaimed space: 行を出力します。
docker container pruneは停止済みコンテナを削除します。書き込み可能レイヤーも削除されるため、ボリューム外にコンテナが書き込んだデータはコンテナとともに削除されます。ボリュームは対象外です。docker image pruneは dangling image のみを削除します。イメージを削除するコマンドの中では、最も安全です。docker builder pruneは dangling build cache を削除します。代償は、次回のビルドが 1 回遅くなることです。docker image prune -aは、どのコンテナからも参照されていないイメージをすべて削除します。再取得または再ビルドが必要になります。docker system pruneは最初の 3 つを一度に実行し、未使用のネットワークも削除します。docker volume pruneは未使用の匿名ボリュームを削除します。docker volume prune -aは名前付きボリュームを含む未使用のボリュームを削除します。データベースを削除するのはこのコマンドです。
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]ボリュームをこの一覧から意図的に外しています。--volumes を追加すると、匿名ボリュームも対象になります。-a を追加すると、イメージの対象が dangling image から未使用のすべてのイメージに広がります。本番ホストで完全な docker system prune -a --volumes -f を実行すると、空き容量を増やそうとしてデータを失うことがあります。
ボリュームの prune でデータベースが削除される理由
このセクションは2回読んでください。
コンテナが接続されていないボリュームは未使用と判定されます。判定条件はこれだけです。Docker は、ボリュームが空かどうか、compose ファイルで引き続き宣言されているかどうか、データベースの唯一のコピーを保持しているかどうかを確認しません。LINKS 0 が docker system df -v に該当する場合、それは prune 対象という意味であり、それ以外の意味はありません。
ここで、通常の操作を2つ続けて実行します。スタックを正常に再起動するために docker compose down を実行します。これはコンテナを削除し、名前付きボリュームを残します。これは仕様どおりの動作です。この時点で、Postgres のボリュームはどのコンテナにも接続されていません。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.匿名ボリュームは、通常、イメージが VOLUME を宣言していて、ユーザーが名前を指定しなかったために Docker が作成したものです。通常は、保持するよう指定していないデータが格納されます。compose ファイルに記述した種類の名前付きボリュームは、-a を追加した場合にだけ削除されます。以前の Docker バージョンでは、引数を付けないコマンドで両方が削除されました。そのため、アップグレード済みのホストで身に付いた習慣をそのまま信頼しないでください。違いを理解するには、名前付きボリュームと bind mount の違いを知る必要があります。bind mount は Docker ボリュームではないため、prune コマンドの対象になることはありません。
削除する前に確認してください。確認対象のボリューム名で myapp_pgdata を置き換えます。
docker volume ls -f dangling=true
docker volume inspect myapp_pgdata
sudo ls -la /var/lib/docker/volumes/myapp_pgdata/_dataボリュームに対する dangling=true フィルターは、空ではなく参照されていないことを意味します。_data を実行すると、実際に格納されている内容を確認できます。そこに pgdata または mysql ディレクトリがある場合は、処理を止め、先にコピーを取得してください。docker compose down -v でも同じ破壊が発生します。このコマンドは compose ファイルで宣言されたすべてのボリュームを削除し、事前に確認を求めません。
Docker ホスト上で、再構築によって復元できないものはボリュームだけです。そのため、ボリュームのデータはサーバー外で実行する restic バックアップに含めてください。入力ミスしたフラグがデータに到達できない場所に保存することが重要です。
何も prune できない場合: コンテナのログファイル
すべてを prune して、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 が未設定です。つまり、サイズに上限がありません。クラッシュループに陥った 1 つのコンテナだけで、パーティションが満杯になるまで書き込み続ける可能性があります。これらのファイルを生成しているコンテナは稼働中であり、定義上 prune の対象にならないため、prune コマンドでは削除されません。
ファイルを削除しないでください。開いているログファイルに対して rm を実行しても、容量は解放されません。Docker デーモンがファイルディスクリプターを開いたまま保持しているため、カーネルはそのハンドルが閉じられるまでブロックを割り当てたままにします。df はまったく移動しません。代わりに truncate してください。同じ inode を維持できるため、デーモンはそのまま書き込みを続けられます。
sudo find /var/lib/docker/containers -name '*-json.log' -exec truncate -s 0 {} +
df -h /これは一時的な対処です。対象コンテナに対する docker logs は現在何も返さず、ファイルは直ちに再び増大し始めます。根本的な対策はローテーションです。次のセクションで説明します。
毎回、前後の値を測定します
prune の結果を推測しないでください。値を測定し、1 つのコマンドを実行し、もう一度測定します。
df -h /
docker system df
docker builder prune -f --filter until=168h
docker image prune -f
docker system df
df -h /2 つの df の出力を比較します。サーバーがサービス提供を継続できるかどうかを決めるのは、この数値だけです。続いて docker system df を確認すると、実際に変化した行と、各 prune が出力する Total reclaimed space: の値が分かります。
df が変化していないのに docker system df が空き容量の増加を示している場合、開いたファイルハンドルが削除済みブロックを保持しています。これは前述のログファイルの問題です。両方が変化し、1 日以内にディスクが再びいっぱいになる場合は、クリーンアップではなく増加が問題です。必要なのはローテーションとスケジュール済みジョブです。
ディスクが再び満杯になるのを防ぐ方法
ログサイズに上限を設定します。 /etc/docker/daemon.json を作成するか編集します。
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}これにより、各コンテナのログサイズが 30 MB に制限されます。log-opts 配下のすべての値は、数値を含めて文字列にする必要があります。再起動する前にファイルを解析できることを確認してください。形式が不正な daemon.json はデーモンの起動自体を妨げ、すべてのコンテナも停止させます。
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"対象を限定して prune を定期実行します。 週次で、dangling image と古い build cache だけを対象にします。定期ジョブには -a や --volumes を絶対に含めないでください。スタックが停止している間にジョブが実行されると、そのスタックのイメージが削除されます。--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最後の行は、スクリプトを最初に手動で 1 回実行し、無人実行される前に出力を確認するためのものです。ファイルには実行権限が必要です。また、名前にドットを含めてはいけません。run-parts は実行権限のないファイルと拡張子の付いたファイルをスキップするためです。
空き容量を監視します。 ディスクが満杯になった後に実行する prune は復旧策です。80 パーセントの時点でのアラートは予防策です。
usage=$(df --output=pcent / | tr -dc '0-9')
[ "$usage" -ge 80 ] && echo "root filesystem at ${usage} percent full"使用中の通知ツールに合わせて、これを cron に登録します。空き容量は状況の半分にすぎません。そのため、VPS のディスクヘルス監視 と組み合わせてください。ディスク障害とディスク容量不足はどちらもコンテナを停止させますが、必要な対処は異なります。
ここまでの説明は、データルートが /var/lib/docker にある標準的なインストールを前提にしています。daemon.json の data-root キーを使って移動した場合は、すべてのコマンドで自分のパスに置き換えてください。新しいサーバーでこのレイアウトを正しく設定することは、VPS での Docker のセットアップ の一部です。誤ったパーティションに 40 GB のコンテナを配置してしまう前に決めておくほうが、はるかに簡単です。
FAQ
Docker system prune はボリュームを削除しますか?
いいえ。引数なしのコマンドは、停止したコンテナ、未使用のネットワーク、dangling イメージ、未使用のビルドキャッシュを削除します。確認プロンプトにも、この対象だけが正確に表示されます。ボリュームが対象になるのは --volumes を追加した場合だけです。Docker Engine 23.0 以降、このフラグの対象は名前付きボリュームではなく匿名ボリュームです。名前付きボリュームは docker volume prune -a と docker compose down -v で削除されます。注意が必要なのはこの2つのコマンドです。
docker prune を実行した後もディスクが満杯なのはなぜですか?
主な原因は2つあります。1つ目は /var/lib/docker/containers/ にあるコンテナのログファイルです。prune コマンドでは削除されず、max-size を設定するまで無制限に増えます。2つ目は、プロセスが開いたまま保持している削除済みファイルです。コンテナの実行中に rm でログを削除すると、daemon はファイルディスクリプターを保持し続けます。そのため、カーネルはブロックを解放せず、df の表示も変わりません。sudo du -xh --max-depth=1 /var/lib/docker と docker system df を比較すると、どちらの状態か確認できます。
docker image prune と docker image prune -a の違いは何ですか?
引数なしのコマンドは dangling イメージだけを削除します。これは、ほぼ常に再ビルドによってタグを失ったイメージです。-a 形式では、既存のコンテナから参照されていないすべてのイメージを削除します。意図的に pull したタグ付きイメージも対象です。docker compose down の後はコンテナがなくなるため、-a によってそのスタックのイメージも削除されます。次回の起動時に再度 pull またはビルドされるため、完全に失われるわけではありません。ただし、低速な回線では長時間待つことになります。
Docker のログでディスクが満杯になるのを防ぐにはどうすればよいですか?
/etc/docker/daemon.json の log-opts 配下で max-size と max-file を設定し、sudo systemctl restart docker で daemon を再起動します。この設定は、再起動後に作成されたコンテナにだけ適用されます。そのため、実行中のコンテナを docker compose up -d --force-recreate で再作成します。compose ファイルでは、logging キーの下でサービスごとに同じ2つのオプションを設定できます。特定のサービスだけが他より大量のログを出す場合に適しています。
cron ジョブで docker system prune を実行しても安全ですか?
引数なしの docker system prune -f は、すべてのスタックを稼働させ続けるホストであれば安全です。ただし、停止したコンテナを削除するため、意図的に停止して後で再起動する予定だったコンテナも削除されます。スケジュール実行には、docker image prune -f と docker builder prune -f --filter until=168h の組み合わせのほうが安全です。増加が速い2つの対象を解放でき、ボリュームには触れません。-a と --volumes は、決してスケジュール実行しないでください。