公開日 2026-08-13

Dockerで「no space left on device」を解決する(WSL2でDockerのディスクとマウント先を見分ける)

docker pull や compose up が no space left on device で落ちるとき、Docker のデータ領域・マウント先・inode のどれが不足しているかを df で切り分け、prune の適用範囲を確かめてから空きを回収する手順を実測で追う。

目次

  1. 1. エラー原文が出てくる2つの層と、検証環境
  2. Docker が返す原文
  3. コンテナの中のコマンドが出す原文
  4. 2. Docker のデータ領域だけを満杯にして再現する
  5. service "app" is not running が続けて出る場合
  6. 3. 満杯なのはどのファイルシステムか
  7. 書き込み先がどのディスクに乗っているかは、compose.yml で決まる
  8. df の空きは、そのまま書き込める量ではない
  9. docker system df はファイルシステムの計測ではない
  10. 4. 3つの不足を切り分ける
  11. inode が尽きた場合の見え方
  12. 5. Docker のデータ領域から空きを回収する
  13. 消えて困るものを先に洗い出す
  14. 削除の範囲を、先にサンドボックスで見る
  15. 警告文が適用範囲を述べている
  16. 実際に消える範囲を確かめる
  17. 洗い出しの結果は、system prune を挟むと変わる
  18. 実行する順番
  19. 6. 片付けと、対処

docker pullno 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 はサンドボックスの中で mountdockerd を動かすために必要です。--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.ymlvolumes の左側を見てください。

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

ContainersSIZE は 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 device0(wsl -d docker-desktop 側で確認)測れない測れないDocker のデータ領域のバイト数5章
できる余裕がある0問わない書き込み先のバイト数bind mount 元の不要なファイルを削除、または置き場所を変える
できる余裕がある余裕がある100%書き込み先の inode書き込み先の不要なファイルを削除(小さいファイルの数を減らす)
上のどれでもない余裕がある余裕がある100% 未満この記事で再現した3つではないWindows の C: の空きと、Docker Desktop の仮想ディスク上限の設定値を確認する(3章)。この経路は再現していないため、以降は扱っていない

1行目と2行目を分けるのは、2章で確認した挙動です。データ領域が満杯なら docker rundocker createdocker 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.ymlvolumes の左側にある .// で始まるパスです(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 を足すと anonymousall に変わります。

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 に空きがあるかどうかで先に分かれます。

シリーズ 4/4

このシリーズ

Dockerのエラーを原文から切り分ける

  1. 1. Dockerで「port is already allocated」を解決する(WSL2/Windowsでの原因特定と対処)
  2. 2. Dockerで「Permission denied」を解決する(WSL2のバインドマウントとUIDの食い違い)
  3. 3. Dockerで「localhostに繋がらない」を解決する(WSL2でコンテナの待受アドレスを見分ける)
  4. 4. Dockerで「no space left on device」を解決する(WSL2でDockerのディスクとマウント先を見分ける) 現在の記事