Fixing 'No Space Left on Device' from Docker Image and Container Buildup
Docker builds and pulls start failing with ENOSPC, even though the host's regular disk usage doesn't look that high. Docker's own storage accumulates in places df alone won't clearly show you.
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:
// /etc/docker/daemon.json
{
"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
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.