docker pull が no space left on device で落ち、docker compose up -d も同じ文字列で止まる。この記事では、そのメッセージがどのファイルシステムの不足を指しているのかを Docker Desktop + WSL2 環境で切り分け、消してよいものだけを消して空きを取り戻すところまでを、実際の出力で追います。非対象は Linux ネイティブの Docker Engine、Kubernetes、本番環境の容量設計です。
1. エラー原文が出てくる2つの層と、検証環境
- Windows 11
- WSL2(Ubuntu)
- Docker Desktop 4.55.0(Docker Engine 29.1.3)
- 再現用サンドボックス:
docker:29.1.3-dind alpine:3.20/debian:12-slim
コマンドは断りがない限り WSL 側のシェルで実行し、Windows 側で実行するものには PowerShell と明記しました。
このエラーの原文が出てくる場所は2か所あります。Docker(デーモン)が返す行と、コンテナの中で動いたコマンドが出す行です。
Docker が返す原文
docker pull はこの形になります。
Error response from daemon: write tmp file: write /var/lib/docker/containerd/daemon/io.containerd.content.v1.content/ingest/0cc116b935383f418a974a752aa33949939110a9f4db785dc2fc5ca2ab3b315f/updatedat.tmp: no space left on device
docker build では、失敗した工程の名前が前に付きます。
ERROR: failed to build: failed to solve: failed to read dockerfile: failed to prepare as 97s9ynckaqbdsyugcr57lvxwq: failed to create prepare snapshot dir: failed to create temp dir: mkdir /var/lib/docker/containerd/daemon/io.containerd.snapshotter.v1.overlayfs/snapshots/new-3408741606: no space left on device
docker run が止まるのは、コンテナを作る段階です。
docker: Error response from daemon: failed to create prepare snapshot dir: failed to create temp dir: mkdir /var/lib/docker/containerd/daemon/io.containerd.snapshotter.v1.overlayfs/snapshots/new-134162707: no space left on device
docker volume create はボリュームの置き場所の作成で落ちます。
Error response from daemon: create app-db-data: error while creating volume root path '/var/lib/docker/volumes/app-db-data': mkdir /var/lib/docker/volumes/app-db-data: no space left on device
docker compose up -d では、ネットワークの作成で先に止まることもあります。
failed to create network docker-no-space-left-on-device-demo_default: Error response from daemon: failed to update bridge store for object type *bridge.networkConfiguration: write /var/lib/docker/network/files/local-kv.db: no space left on device
パスの中身は Engine のバージョン次第です。29.1.3 で採ると containerd/daemon/io.containerd.* を含み、28.5.2 で同じ操作を行うと overlay2 配下と tmp 配下になりました。パス名から読み取れるのは containerd 側のスナップショッタが使われていることまでで、どのバージョンから既定が切り替わったかはここでは確かめていません。
docker: Error response from daemon: mkdir /var/lib/docker/overlay2/af9265a03586e3df60eabe9ed73357d02f5d5891b378201c2cc24f18cf63a4b3-init: no space left on device
write /var/lib/docker/tmp/GetImageBlob1744276480: no space left on device
バージョンが違っても共通するのは、末尾の no space left on device と、パスが /var/lib/docker で始まる点です。手元の出力とパスが食い違うときは、この2つで読み合わせてください。
コンテナの中のコマンドが出す原文
コンテナが起動して、その中の書き込みが失敗した場合の文言は別系統です。Alpine の busybox ではこうなります。
dd: error writing '/data/fill': No space left on device
touch: /data/f116: No space left on device
sh: write error: No space left on device
Debian 系の GNU coreutils は、対象を引用符で囲んだ形です。
cp: cannot create regular file '/data/zz2': No space left on device
touch: cannot touch '/data/zzz': No space left on device
tee: /data/c: No space left on device
Debian の sh(dash)でリダイレクトが失敗したときは、容量不足と分かる語が出ません。
sh: 1: echo: echo: I/O error
この行には容量不足を示す語がありません。同じコンテナで df を取れば区別が付きます(3章)。
採取した範囲では、Docker が返す行は小文字の no、コンテナの中の C 系コマンドは大文字の No で始まりました。ただしこれは書いた言語の慣習によるもので、コンテナの中で動く Go 製のプログラムなら小文字で出ることもあります。確実な判定材料は大小ではなく、その行を出したのが docker コマンドなのか、コンテナの中のプロセスなのかという点です。 両者は別のファイルシステムを指していることがあり、4章の切り分けはここが起点です。
2. Docker のデータ領域だけを満杯にして再現する
ホストの Docker のデータ領域を実際に埋めると、同じマシンで動いている他のコンテナを巻き込みます。ここではコンテナの中に使い捨ての dockerd を立て、その専用のデータ領域だけを満杯にします。 外側の Docker のイメージ・ボリューム・稼働中のコンテナには手を付けない構成です。
これは症状を手元で観察するための再現であって、復旧手順ではありません。 サンドボックスの 300MB は外側の Docker のデータ領域に置かれ、docker:29.1.3-dind の取得にも空きが要ります。いま満杯で困っている環境では実行しないでください。 空きのある環境か、復旧後に読む章です。急いで直したい場合は3章へ進んでください。
Windows のターミナルで wsl を実行して Ubuntu に入り、作業ディレクトリを作成します。
# Windows側から始める場合のみ実行
# wsl
mkdir -p ~/projects/docker-no-space-left-on-device-demo
cd ~/projects/docker-no-space-left-on-device-demo
code .
sandbox-full-disk.sh を作成します。300MB のファイルを ext4 でフォーマットし、それを使い捨ての dockerd のデータ領域としてマウントしてから満杯にする内容です。
#!/bin/sh
# 使い捨ての dockerd を、300MB しかないファイルシステムの上に立てる。
# ホストの Docker とは別プロセス・別データ領域なので、外側には影響しない。
# 300MB のファイルを ext4 でフォーマットし、データ領域としてマウントする
dd if=/dev/zero of=/small.img bs=1M count=300 >/dev/null 2>&1
mkfs.ext4 -q -F /small.img
mkdir -p /var/lib/docker
mount -o loop /small.img /var/lib/docker
# このサンドボックス専用の dockerd を起動する
dockerd --data-root /var/lib/docker >/tmp/dockerd.log 2>&1 &
i=0
while [ $i -lt 40 ]; do docker info >/dev/null 2>&1 && break; i=$((i+1)); sleep 1; done
# 空きがあるうちに小さいイメージを1つ置いておく
docker pull alpine:3.20 >/dev/null 2>&1
echo "===== 満杯にする前 ====="
df -h /var/lib/docker | tail -1
# 残りを埋めて満杯にする
dd if=/dev/zero of=/var/lib/docker/BALLAST bs=1M count=400 >/dev/null 2>&1
echo "===== 満杯にした後 ====="
df -h /var/lib/docker | tail -1
echo "===== docker pull ====="
docker pull nginx:1.27-alpine
echo "exit=$?"
echo "===== docker build ====="
mkdir -p /build && cd /build
printf 'FROM alpine:3.20\nRUN echo hello > /hello\n' > Dockerfile
docker build -t buildtest .
echo "exit=$?"
cd /
echo "===== docker run ====="
docker run --rm alpine:3.20 echo hello
echo "exit=$?"
echo "===== docker volume create ====="
docker volume create app-db-data
echo "exit=$?"
echo "===== docker compose up -d ====="
mkdir -p /demo && cd /demo
printf 'services:\n app:\n image: alpine:3.20\n command: ["sleep","infinity"]\n' > compose.yml
docker compose -p docker-no-space-left-on-device-demo up -d
echo "exit=$?"
docker compose -p docker-no-space-left-on-device-demo exec app sh -c "echo inside"
echo "exit=$?"
cd /
echo "===== docker system df ====="
docker system df
次のコマンドで実行します。--privileged はサンドボックスの中で mount と dockerd を動かすために必要です。--rm を付けているので、終了時にサンドボックスは消えます。
--privileged はコンテナの隔離を外し、ホストのデバイスへの権限を与えます。 上のスクリプトと公式イメージの組み合わせでだけ使ってください。ホストの docker.sock や /var/lib/docker を -v で渡す形に改変すると、外側の Docker を巻き込みます。--rm が守るのは終了後の後始末だけで、実行中の権限は狭めません。
docker run --rm -i --privileged --entrypoint sh docker:29.1.3-dind < sandbox-full-disk.sh
出力です。
===== 満杯にする前 =====
/dev/sde 271.1M 11.6M 240.6M 5% /var/lib/docker
===== 満杯にした後 =====
/dev/sde 271.1M 267.1M 0 100% /var/lib/docker
===== docker pull =====
Error response from daemon: write tmp file: write /var/lib/docker/containerd/daemon/io.containerd.content.v1.content/ingest/0cc116b935383f418a974a752aa33949939110a9f4db785dc2fc5ca2ab3b315f/updatedat.tmp: no space left on device
exit=1
===== docker build =====
#0 building with "default" instance using docker driver
#1 [internal] load build definition from Dockerfile
#1 ERROR: failed to prepare as 97s9ynckaqbdsyugcr57lvxwq: failed to create prepare snapshot dir: failed to create temp dir: mkdir /var/lib/docker/containerd/daemon/io.containerd.snapshotter.v1.overlayfs/snapshots/new-3408741606: no space left on device
------
> [internal] load build definition from Dockerfile:
------
ERROR: failed to build: failed to solve: failed to read dockerfile: failed to prepare as 97s9ynckaqbdsyugcr57lvxwq: failed to create prepare snapshot dir: failed to create temp dir: mkdir /var/lib/docker/containerd/daemon/io.containerd.snapshotter.v1.overlayfs/snapshots/new-3408741606: no space left on device
exit=1
===== docker run =====
docker: Error response from daemon: failed to create prepare snapshot dir: failed to create temp dir: mkdir /var/lib/docker/containerd/daemon/io.containerd.snapshotter.v1.overlayfs/snapshots/new-134162707: no space left on device
Run 'docker run --help' for more information
exit=125
===== docker volume create =====
Error response from daemon: create app-db-data: error while creating volume root path '/var/lib/docker/volumes/app-db-data': mkdir /var/lib/docker/volumes/app-db-data: no space left on device
exit=1
===== docker compose up -d =====
Network docker-no-space-left-on-device-demo_default Creating
Network docker-no-space-left-on-device-demo_default Error Error response from daemon: failed to update bridge store for object type *bridge.networkConfiguration: write /var/lib/docker/network/files/local-kv.db: no space left on device
failed to create network docker-no-space-left-on-device-demo_default: Error response from daemon: failed to update bridge store for object type *bridge.networkConfiguration: write /var/lib/docker/network/files/local-kv.db: no space left on device
exit=1
service "app" is not running
exit=1
===== docker system df =====
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 1 0 11.68MB 9.226kB (0%)
Containers 0 0 0B 0B
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0B
データ領域が満杯だと、コンテナを新しく作れません。 docker run は終了コード 125 で止まり、echo hello は実行されないままです。docker create と、既存コンテナの docker start も同じ状態では失敗します。名前付きボリュームの作成が通らないのは、ボリュームの実体が /var/lib/docker/volumes に置かれるためです。
ここで言えるのは「新しく作れない」までで、「アプリ側のエラーが出ない」ではありません。 満杯になる前から動いていたコンテナはそのまま Up を保ち、その中の書き込みだけが大文字の No で失敗します。4章の1列目が「作成できるか」を聞いているのは、この2つを混同しないためです。すでに動いているコンテナがあるかどうかではなく、いま新しく作れるかどうかで判定してください。
docker system df の行も見てください。ファイルシステムは 100% なのに、RECLAIMABLE は 9.226kB しかありません。この差の説明は5章です。
service "app" is not running が続けて出る場合
docker compose up -d がこのエラーで落ちた直後に docker compose exec を打つと、容量とは関係のない文言に化けます。
failed to create network docker-no-space-left-on-device-demo_default: Error response from daemon: failed to update bridge store for object type *bridge.networkConfiguration: write /var/lib/docker/network/files/local-kv.db: no space left on device
service "app" is not running
2つ目の文面が伝えるのは「コンテナが動いていない」ことだけで、容量には触れません。同じメッセージはポート衝突でコンテナが起動しなかったときにも出るため、切り分けは Dockerで「port is already allocated」を解決する(WSL2/Windowsでの原因特定と対処) の4章です。docker compose up -d のほうのログを遡って、最初に落ちた行を読んでください。
なお容量不足でネットワークの作成から落ちた場合、コンテナは Created としても残りません(docker compose ps -a が空になります)。あちらの記事が使う docker inspect での原文の回収は使えないので、up の出力そのものが唯一の原文になります。
3. 満杯なのはどのファイルシステムか
Docker Desktop + WSL2 で書き込み先になりうるディスクは3つです。どれが不足しても同じ文字列が出るため、まず数字を取ります。
Docker のデータ領域を読むには、コンテナを1つ動かせば足ります。コンテナの / がそのまま乗っているためです。
docker run --rm alpine:3.20 df -h /
Filesystem Size Used Available Use% Mounted on
overlay 1006.9G 75.4G 880.2G 8% /
もっともこの方法は、データ領域が満杯になった時点で使えなくなります。 2章のとおりコンテナを作れないためです。その場合は Docker Desktop が使っている WSL のディストリビューションから直接読みます。
wsl -d docker-desktop -- df -h /mnt/docker-desktop-disk
Filesystem Size Used Available Use% Mounted on
/dev/sde 1006.9G 75.4G 880.2G 8% /mnt/docker-desktop-disk
同じ時点で両方を実行すると、Available は一致します。コンテナが起動する状況なら前者、docker run 自体が no space left on device で落ちるなら後者を使ってください。
後者は Docker Desktop が WSL2 バックエンドで動いている場合の手段です。ディストリビューション名とマウント先のパスはバージョンや設定で変わりうるため、There is no distribution with the supplied name. や can't find mount point が返る場合は、Docker Desktop の設定画面でディスクイメージの保存先を確認してください。
WSL 側のディスクは、bind mount の元になっている場所です。WSL のシェルでそのまま確認します。
df -h ~
Filesystem Size Used Avail Use% Mounted on
/dev/sdf 1007G 9.3G 947G 1% /
Windows 側のディスクは PowerShell で確認します。
Get-PSDrive C
書き込み先がどのディスクに乗っているかは、compose.yml で決まる
3つのうちどれを見るかを決めるのは、失敗した書き込みの行き先です。compose.yml の volumes の左側を見てください。
services:
app:
volumes:
- ./src:/workspace # bind mount: WSL 側のディスク
- app-db-data:/var/lib/postgresql/data # named volume: Docker のデータ領域
./や/で始まるパスは bind mount です。実体は WSL 側(または Windows 側)にあり、Docker のデータ領域とは別のディスクになります- 名前だけのものは named volume です。実体は
/var/lib/docker/volumesで、Docker のデータ領域そのものにあります - コンテナの中の、どこにもマウントしていない場所(
/tmpなど)への書き込みも Docker のデータ領域です
この違いは df にそのまま出ます。named volume をマウントしたコンテナの中で / と マウント先を並べると、同じ数字になります。
Filesystem Size Used Available Use% Mounted on
overlay 364.6M 11.6M 329.0M 3% /
/dev/loop2 364.6M 11.6M 329.0M 3% /data
named volume への書き込みが No space left on device になったなら、不足しているのは Docker のデータ領域です。 ホスト側に app-db-data という名前のディレクトリを探しても見つかりません。回収は5章になります。
df の空きは、そのまま書き込める量ではない
WSL 側と Docker 側は、どちらも Windows 上の仮想ディスクファイル(.vhdx)の中身です。この検証環境では、Docker 側が 880.2G、WSL 側が 947G の空きを報告していました。一方、それらを収めている C: の空きは 112.78 GB でした。
報告された空きの合計は、C: の物理的な空きを大きく超えた数字です。仮想ディスクは使った分だけ実ファイルが育つ形式で、df が出すのは上限であって、裏付けのある空き容量ではありません。
この検証では C: を実際に枯渇させておらず、そのとき何が起きるかは未確認です。ただし、df に十分な空きが出ているのに書き込みが失敗する場合、C: の空きを見る価値はあります。
docker system df はファイルシステムの計測ではない
2章で RECLAIMABLE が 9.226kB だった件に戻ります。docker system df が数えるのは Docker が管理しているオブジェクトだけで、データ領域の使用量とは別物です。
分かりやすい例がコンテナのログです。標準出力に大量に書くコンテナを1つ動かし、ログの実サイズと docker system df を並べました。
24.7M /var/lib/docker/containers/3370598a7e93b2d9477fc194b5195890544de83a5d06a67058cdf7a8c0ce65f3/3370598a7e93b2d9477fc194b5195890544de83a5d06a67058cdf7a8c0ce65f3-json.log
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 1 1 12.22MB 12.22MB (100%)
Containers 1 1 4.096kB 0B (0%)
Local Volumes 0 0 0B 0B
Build Cache 0 0 0B 0B
Containers の SIZE は 4.096kB です。24.7M のログは数えられていません。ファイルシステム側は 12.3M から 37.1M へ増えており、増分はログのサイズと同じ値でした。
このとき docker system prune -f を実行しても Total reclaimed space: 0B で、空きは戻りません。ログは稼働中のコンテナに属しているためです。コンテナを削除すると 12.4M まで戻りました。
docker system df が「消せるものはない」と言っていても、データ領域が満杯であることはあります。 判断には df を使ってください。
4. 3つの不足を切り分ける
判定に使う材料は4つです。コンテナを作成できるか、Docker のデータ領域の空き、書き込み先の空き、書き込み先の inode 使用率になります。書き込み先とデータ領域は別のファイルシステムなので、df -h は2か所で取ります。
1列目はいま新しくコンテナを作れるかです。すでに動いているコンテナがあるかどうかではありません(2章)。作れない場合はデータ領域の測定に wsl -d docker-desktop のほうを使います(3章)。2行目以降の「書き込み先」は、3章で判別した bind mount 側を指します。named volume とコンテナ内の非マウント領域は書き込み先がデータ領域そのものなので、1行目で扱います。
| コンテナ作成 | データ領域の df -h | 書き込み先(bind mount 側)の df -h | 同じく df -i | 不足しているもの | 行き先 |
|---|---|---|---|---|---|
できない(daemon が no space left on device) | 0(wsl -d docker-desktop 側で確認) | 測れない | 測れない | Docker のデータ領域のバイト数 | 5章 |
| できる | 余裕がある | 0 | 問わない | 書き込み先のバイト数 | bind mount 元の不要なファイルを削除、または置き場所を変える |
| できる | 余裕がある | 余裕がある | 100% | 書き込み先の inode | 書き込み先の不要なファイルを削除(小さいファイルの数を減らす) |
| 上のどれでもない | 余裕がある | 余裕がある | 100% 未満 | この記事で再現した3つではない | Windows の C: の空きと、Docker Desktop の仮想ディスク上限の設定値を確認する(3章)。この経路は再現していないため、以降は扱っていない |
1行目と2行目を分けるのは、2章で確認した挙動です。データ領域が満杯なら docker run も docker create も docker start も止まるため、新しいコンテナを1つも作れません。 そのとき書き込み先の df は測れない——測るためのコンテナが起動しないためです。
2行目の df -i を「問わない」にしているのは、バイト数と inode が同時に尽きる状態があるためです。両方尽きているなら、先にバイト側を空けます(不要なファイルを消せば inode も戻ります)。3行目はバイト数に空きがあるのに No space left on device が出る場合で、ここだけ df -i が判定材料になります。
inode が尽きた場合の見え方
小さいファイルを大量に作ると、この状態を作れます。実際の出力がこれです。
touch: /data/f116: No space left on device
このときの df はこうなりました。
/dev/loop3 58.7M 48.0K 54.2M 0% /data
/dev/loop3 128 128 0 100% /data
上が df -h、下が df -i です。バイト数の使用率は 0% で、空きは 54.2M と出ています。df -h だけを見ていると、このケースは説明が付きません。
GNU coreutils 側で同じ状態を作ったときの出力がこれです。
touch: cannot touch '/data/zzz': No space left on device
cp: cannot create regular file '/data/zz2': No space left on device
書き込み先の空きと inode は、その場所をマウントしたコンテナから2つまとめて読めます。<bind mount 元のパス> は compose.yml の volumes の左側にある ./ か / で始まるパスです(3章)。
docker run --rm -v <bind mount 元のパス>:/data alpine:3.20 df -h /data
docker run --rm -v <bind mount 元のパス>:/data alpine:3.20 df -i /data
flowchart TD
A["no space left on device"] --> B{"いま新しくコンテナを作れるか"}
B -->|"作れない"| C["データ領域を測る<br/>wsl -d docker-desktop -- df -h /mnt/docker-desktop-disk"]
C --> C2{"Available"}
C2 -->|"0"| F["データ領域が不足 → 5章"]
C2 -->|"余裕がある"| G["この記事の3つではない<br/>Windows の C: と仮想ディスク上限を確認"]
B -->|"作れる"| K{"失敗した書き込みの行き先は"}
K -->|"named volume / 非マウント領域"| F
K -->|"bind mount"| D["その元を df -h と df -i で測る"]
D --> H{"振り切れているのは"}
H -->|"df -h が 0"| J["書き込み先のバイト数が不足"]
H -->|"df -h は余裕・df -i が 100%"| I["書き込み先の inode が不足"]
H -->|"どちらも余裕がある"| G
5. Docker のデータ領域から空きを回収する
この章のコマンドは削除を伴います。prune 系は、確認を挟まずに実行すると停止中のプロジェクトのデータまで消せます。 順番に確認してから実行してください。
消えて困るものを先に洗い出す
prune は対象ごとに消すものが違うため、確認も対象ごとに分けます。
まず docker system prune が消すのは、停止中のコンテナ・使われていないネットワーク・宙に浮いたイメージ・ビルドキャッシュです。停止中のコンテナには、あとで再開したいものや、失敗の原因を調べたい Created の残骸が含まれます。 先に一覧を見てください。
docker ps -a
コンテナの中だけにデータを置いている場合、それも一緒に消えます。データの所在は Mounts で確認できます。<container> は上の一覧に出た NAMES に置き換えてください。
docker inspect <container> --format '{{range .Mounts}}{{.Type}} {{.Source}} -> {{.Destination}}{{"\n"}}{{end}}'
次に、どのボリュームがどのコンテナからも使われていないかを一覧にします。読み取りだけのコマンドです。
docker volume ls -f dangling=true
「使われていない」は「不要」と同じ意味ではありません。停止中のプロジェクトのデータベースが入っているボリュームも、ここに並びます。 中身の確認が先です。
for v in $(docker volume ls -f dangling=true -q); do
echo "--- $v"
docker run --rm -v "$v":/v alpine:3.20 sh -c "du -sh /v; ls -A /v"
done
削除の範囲を、先にサンドボックスで見る
prune は取り消せません。自分の Docker で試す前に、2章と同じ使い捨ての dockerd で挙動を確認できます。 こちらはデータ領域を満杯にせず、代わりに置くのは「中身のある名前付きボリューム」と「匿名ボリューム」が1つずつです。
sandbox-prune.sh を作成します。
#!/bin/sh
# prune が何をどこまで消すかを、使い捨ての dockerd で先に試す。
dd if=/dev/zero of=/disk.img bs=1M count=900 >/dev/null 2>&1
mkfs.ext4 -q -F /disk.img
mkdir -p /var/lib/docker && mount -o loop /disk.img /var/lib/docker
dockerd --data-root /var/lib/docker >/tmp/dockerd.log 2>&1 &
i=0
while [ $i -lt 40 ]; do docker info >/dev/null 2>&1 && break; i=$((i+1)); sleep 1; done
docker pull alpine:3.20 >/dev/null 2>&1
# 名前付きボリュームに「DBの実データ」を置く
docker volume create app-db-data >/dev/null
docker run --rm -v app-db-data:/data alpine:3.20 \
sh -c "dd if=/dev/zero of=/data/precious.db bs=1M count=30" >/dev/null 2>&1
# 匿名ボリュームを1つ作る
docker run --name anon-owner -v /anon alpine:3.20 true >/dev/null 2>&1
docker rm anon-owner >/dev/null
echo "===== 使われていないボリュームを先に洗い出す ====="
docker volume ls -f dangling=true
echo "===== それぞれの中身を確認する ====="
for v in $(docker volume ls -f dangling=true -q); do
echo "--- $v"
docker run --rm -v "$v":/v alpine:3.20 sh -c "du -sh /v; ls -A /v"
done
echo "===== docker volume prune の適用範囲 ====="
echo n | docker volume prune
echo "===== docker volume prune -a の適用範囲 ====="
echo n | docker volume prune -a
echo "===== docker volume prune -f を実行 ====="
docker volume prune -f
echo "===== 名前付きボリュームは残っているか ====="
docker volume ls
docker run --rm -v app-db-data:/data alpine:3.20 sh -c "du -sh /data; ls -A /data"
echo "===== docker volume prune -a -f を実行 ====="
docker volume prune -a -f
echo "===== 実行後 ====="
docker volume ls
2章と同じ形で実行します。
docker run --rm -i --privileged --entrypoint sh docker:29.1.3-dind < sandbox-prune.sh
洗い出しの部分の出力です。
===== 使われていないボリュームを先に洗い出す =====
DRIVER VOLUME NAME
local app-db-data
local cefaeb16743f28dd0015867153b453d9fb7a5d88780b8f0513c3a35140253124
===== それぞれの中身を確認する =====
--- app-db-data
30.0M /v
precious.db
--- cefaeb16743f28dd0015867153b453d9fb7a5d88780b8f0513c3a35140253124
4.0K /v
app-db-data は使用中のコンテナが無いだけで、中身は残っています。これを消すかどうかが、次の節の分かれ目です。
警告文が適用範囲を述べている
prune 系は実行前に警告を出します。この文面が、そのコマンドの適用範囲そのものです。 フラグの意味を記憶に頼らず、手元のバージョンが出す文面を読んでください。
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]
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
Are you sure you want to continue? [y/N]
ボリュームの行が all anonymous volumes である点が効きます。docker volume prune も同じ範囲です。
WARNING! This will remove anonymous local volumes not used by at least one container.
-a を足すと anonymous が all に変わります。
WARNING! This will remove all local volumes not used by at least one container.
実際に消える範囲を確かめる
警告文のとおりに動くかを、サンドボックスで確認しました。docker volume prune -f を実行した結果です。
===== docker volume prune -f を実行 =====
Deleted Volumes:
cefaeb16743f28dd0015867153b453d9fb7a5d88780b8f0513c3a35140253124
Total reclaimed space: 0B
===== 名前付きボリュームは残っているか =====
DRIVER VOLUME NAME
local app-db-data
30.0M /data
precious.db
消えたのは匿名ボリュームだけで、app-db-data は 30MB の中身ごと残りました。続けて -a を付けた場合です。
===== docker volume prune -a -f を実行 =====
Deleted Volumes:
app-db-data
Total reclaimed space: 31.46MB
===== 実行後 =====
DRIVER VOLUME NAME
precious.db は戻りません。Engine 29.1.3 で名前付きボリュームを消すのは docker volume prune -a のほうで、docker system prune -a --volumes ではありませんでした。 28.5.2 でも同じ結果です。この2つのバージョンで確認した範囲の話なので、手元では実行前に警告文を読んで確かめてください。
洗い出しの結果は、system prune を挟むと変わる
「使われていない」の判定は、コンテナが消えると変わります。 停止中のコンテナが参照しているボリュームは、dangling=true には出ません。
--- stopped-owner の状態 ---
stopped-owner Created
--- 停止中コンテナが参照している状態での dangling 一覧 ---
DRIVER VOLUME NAME
このコンテナを削除したあとの一覧がこれです。
--- 停止中コンテナを削除した後の dangling 一覧 ---
DRIVER VOLUME NAME
local app-db-data
docker system prune は停止中のコンテナを消します。それを実行した時点で、最初に取った洗い出しの結果は古くなります。 洗い出しで「参照されているから対象外」と判断したボリュームが、system prune のあとには docker volume prune -a の対象に入っている、という順序です。
docker volume prune -a を打つ前には、一覧を読み直すのではなく取り直してください。
実行する順番
影響の小さいものから試すと、消しすぎを避けられます。
# 1. 停止中のコンテナ・使われていないネットワーク・宙に浮いたイメージ・ビルドキャッシュ
docker system prune
# 2. ビルドキャッシュだけを狙う
docker builder prune
# 3. まだ足りないとき: どのコンテナからも参照されていないイメージも消す
docker system prune -a
2番目の警告文は範囲が狭く、ボリュームにもイメージにも触れません。
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N]
3番目を最後に置いているのは、消したイメージを次のビルドや起動で取得し直すことになるためです。警告文のとおり対象は「どのコンテナにも紐づいていないイメージ」で、停止中のコンテナが先に消えるぶん、残るのは稼働中のコンテナが使っているイメージだけになります。回線と時間の都合が付くときに実行してください。
3章のログの件があるため、稼働中のコンテナが抱えるログは prune では戻りません。ログが原因なら、直す場所は compose.yml の上限指定です。
services:
app:
image: alpine:3.20
logging:
driver: json-file
options:
max-size: "1m"
max-file: "3"
同じ量を標準出力に書くコンテナで、この指定の有無を比べました。指定が無い側は、ログが 1 ファイルで 32.5M まで育った状態です。指定した側は 3 ファイルに分割され、合計 2.0M で頭打ちになりました。
980.0K <container-id>-json.log.1
56.0K <container-id>-json.log
980.0K <container-id>-json.log.2
コンテナ1つあたりの上限は max-size × max-file です。計測値の 2.0M が 3M に届いていないのは、書き込みが終わった時点で最後のファイルがまだ 56.0K だったためで、回り切れば 3M 近くで頭打ちになります。
docker volume prune -a は最後の手段にしてください。実行する直前に、洗い出しを取り直します。 ここまでの手順で system prune を通しているなら、最初に取った一覧はもう古い状態です。
docker volume ls -f dangling=true
for v in $(docker volume ls -f dangling=true -q); do
echo "--- $v"
docker run --rm -v "$v":/v alpine:3.20 sh -c "du -sh /v; ls -A /v"
done
この一覧に消えて困るものがあれば、先に退避します。退避の前に、そのボリュームを参照しているコンテナが無いことを確認してください。稼働中のデータベースをそのまま固めると、書き込み途中のファイルが混ざります。
# <volume> は自分の環境に置き換える。出力が空でないなら、そのコンテナを先に停止する
docker ps -a --filter volume=<volume> --format "{{.Names}} {{.Status}}"
保存先には、いま不足しているディスクとは別の場所を選んでください。同じディスクへ書くと、退避の途中で容量が尽きます。
# 退避(<volume> と <保存先の絶対パス> を置き換える)
docker run --rm -v <volume>:/from -v <保存先の絶対パス>:/to alpine:3.20 \
tar --numeric-owner -czf /to/<volume>.tar.gz -C /from .
戻すときは、同じ形で展開します。復元は root で行ってください。 一般ユーザーで展開すると所有者を復元できず、tar: can't make dir ...: Permission denied で止まります。
# 復元
docker volume create <volume>
docker run --rm -v <volume>:/to -v <保存先の絶対パス>:/from alpine:3.20 \
tar --numeric-owner -xzf /from/<volume>.tar.gz -C /to
--numeric-owner を付けているのは、アーカイブが所有者を数値と名前の両方で持つためです。名前で解決すると、/etc/passwd の異なるイメージで展開したときに別の UID へ落ちることがあります。所有者とパーミッションは、この形なら往復して一致します。
--numeric-owner の位置に注意してください。tar czf --numeric-owner <ファイル> と書くと、f がオプション名のほうをファイル名として受け取り、アーカイブが作られないまま tar: <ファイル>: No such file or directory で終わります。
退避が終わったら、アーカイブの中身を確認してから prune に進んでください。
docker run --rm -v <保存先の絶対パス>:/b alpine:3.20 tar tvzf /b/<volume>.tar.gz
6. 片付けと、対処
この記事のサンドボックスは --rm 付きで起動しているので、終了時にコンテナは消えます。ホスト側に残るのは取得した docker:29.1.3-dind のイメージだけです。
docker image rm docker:29.1.3-dind
作業ディレクトリごと消す場合は、対象を確認してから削除します。
cd ~/projects
ls -d docker-no-space-left-on-device-demo
rm -rf docker-no-space-left-on-device-demo
このディレクトリにはスクリプトしか置いていないため、消えて困るものはありません。自分の構成へ読み替えるときは、rm -rf の対象に .git や依存ディレクトリが含まれないかを先に確認してください。
症状ごとの対処は次の6件です。4章の表と対応しています。
docker pull/build/runが daemon のエラーで落ちる → 2章(データ領域が満杯のときの挙動)と5章(回収)- コンテナは動いているが、中のコマンドだけが
No space left on device→ 3章で書き込み先を判別する。named volume ならデータ領域側なので5章、bind mount ならその元をdf - bind mount 元の
df -hが 0 → その場所の不要なファイルを削除(4章の2行目) df -hに空きがあるのに書き込みが失敗する → 4章(df -iで inode を確認)docker system dfが「消せるものはない」と言うのに満杯 → 3章でログの実サイズを確認し、5章でコンテナごと削除してloggingの上限を付けるsystem pruneのあとにvolume prune -aを打つ → 5章(洗い出しを取り直してから)
同じシリーズで、コンテナが作ったファイルを編集できなくなる話は Dockerで「Permission denied」を解決する(WSL2のバインドマウントとUIDの食い違い) にあります。書き込みが失敗する点は同じでも、あちらの原因は所有者の食い違いです。df に空きがあるかどうかで先に分かれます。