Fixing 'No Space Left on Device' from Docker Image and Container Buildup
Recover Docker disk space methodically by measuring images, containers, caches, volumes, and logs before pruning, then add safe recurrence controls.
docker build or docker pull fails with no space left on device, but a general df -h on the host doesn’t obviously show a full disk — because Docker’s actual storage (images, stopped containers, build cache, unused volumes) lives inside its own storage driver’s directory structure, which regular disk-usage checks don’t break down clearly on their own.
Step 1: ask Docker directly how much space it’s using
docker system df
This is the right first command, always — it reports space used by images, containers, local volumes, and the build cache separately, immediately showing which of these is actually the largest contributor rather than guessing.
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 47 12 18.2GB 14.1GB (77%)
Containers 23 3 421MB 398MB (94%)
Local Volumes 8 2 3.2GB 2.9GB (90%)
Build Cache 142 0 9.8GB 9.8GB (100%)
Step 2: the fast, broad fix
docker system prune -a --volumes
This removes every stopped container, every network not used by at least one container, every image not referenced by at least one container, and every unused local volume. The -a flag is important — without it, prune only removes dangling images (untagged layers), not all unused ones, which is usually far less space reclaimed than what’s actually available to clean up.
Be deliberate with --volumes specifically — it removes unused named volumes, which may hold data you still need (a database’s data directory, for instance, if its container was stopped but not yet deleted). Run docker system df -v first if you’re unsure what’s in play before including this flag.
Step 3: if build cache specifically is the largest offender
Build cache can grow substantially with frequent multi-stage builds, especially in CI environments building many similar images repeatedly:
docker builder prune -a
Step 4: find genuinely large individual images worth investigating
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | sort -k3 -h -r
An unexpectedly large image often points to a Dockerfile that isn’t using multi-stage builds properly — leaving build tools, package caches, or intermediate layers baked permanently into the final image, discussed in more depth in this blog’s coverage of building minimal container images.
Step 5: check for orphaned containers still holding disk via logs
Long-running containers with verbose logging and no log rotation configured can silently consume enormous disk space in their JSON log files, entirely separate from image/container storage:
docker inspect --format='{{.LogPath}}' mycontainer
du -sh $(docker inspect --format='{{.LogPath}}' mycontainer)
Configuring a log rotation limit at the daemon level prevents this from recurring. Docker reads these settings from /etc/docker/daemon.json, which must be strict JSON — the file format does not accept comments:
{
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
Step 6: for Kubernetes nodes running containerd/CRI-O directly
On a Kubernetes node (rather than a standalone Docker host), the equivalent cleanup goes through crictl instead, since kubelet and the CRI runtime manage image/container lifecycle independently of any standalone Docker installation:
crictl rmi --prune
Check the filesystem failure, not only Docker’s summary
ENOSPC can mean exhausted bytes or exhausted inodes. Run df -h and df -i on the filesystem that actually backs Docker, and inspect docker info for its data root and storage driver. Docker Desktop stores Linux data inside a managed virtual machine, so host free space and the application’s disk allocation are not interchangeable.
Do not manually delete files beneath Docker’s data directory while the daemon is running. Layer metadata and content references are managed together; removing selected directories can corrupt images or containers without reclaiming space safely. Use Docker’s inspect, remove, builder, and prune commands against objects whose ownership you understand.
Turn cleanup into a bounded policy
Prefer targeted removal before a global prune: delete identified stopped containers, old build cache, and reproducible images. Back up named volumes containing required state and verify the backup before removal. In BuildKit environments, use documented cache retention controls so recent layers remain useful while old cache is reclaimed.
After cleanup, rerun docker system df -v, df -h, and the failed build or pull. Configure log rotation for newly created containers, then verify it on a disposable workload; daemon defaults generally do not rewrite existing container logging configuration. On Kubernetes nodes, let kubelet image garbage collection manage normal reclamation and diagnose disk-pressure thresholds rather than scheduling an unrestricted Docker prune.
Why this accumulates silently in the first place
Docker’s layer-caching model — the exact thing that makes builds fast by reusing unchanged layers — is also what causes storage to accumulate quietly over time: every build, every pulled image version, and every stopped-but-not-removed container leaves data behind until something explicitly cleans it up. docker system df run periodically (or as a scheduled maintenance task on CI runners and long-lived hosts) catches this building up before it becomes an actual build-blocking failure.
Related:
Sources: