Your build just died with no space left on device, or a docker pull stopped halfway, or a container refuses to start. Docker has filled its data directory and now everything is blocked. The fast way out is two commands. The careful way out — the one that doesn't delete data you'll miss — takes about five minutes and starts with looking before you delete.
Here's the short version, in escalation order. Run the diagnostic first, then climb the ladder only as far as you need:
docker system df # see what's actually using space
docker system prune # safe: stopped containers, unused networks, dangling images, build cache
docker system prune -a # also removes ALL unused images (re-pull later)
docker builder prune -a # nuke build cache specifically (often the real hog)
Stop as soon as you have enough room. Don't open with docker system prune -af --volumes — that --volumes flag is how people lose database data. More on that below.
Why this happens
Everything Docker stores — images, container writable layers, named and anonymous volumes, and build cache — lives under one directory on Linux, /var/lib/docker. When the partition holding that directory fills up, any operation that needs to write fails with no space left on device. Build cache is usually the surprise: every RUN step in a Dockerfile can leave an intermediate layer behind, and on a CI box those pile up fast.
There's a second cause that looks identical but isn't a byte problem at all: running out of inodes. We'll get to that, because the usual fix won't help if that's what bit you.
Step 1 — Diagnose before deleting
docker system df gives you the breakdown and, crucially, a RECLAIMABLE column showing what a prune could actually free:
docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 42 7 18.6GB 14.2GB (76%)
Containers 11 3 1.1GB 980MB (87%)
Local Volumes 9 4 6.3GB 2.1GB (33%)
Build Cache 156 0 22.4GB 22.4GB (100%)
If one row dwarfs the rest, you know where to aim. Add -v for a per-object listing so you can see exactly which images or cache entries are the offenders:
docker system df -v
Then check the host filesystem itself:
df -h /var/lib/docker # bytes used/free
df -i /var/lib/docker # inodes used/free
If df -h says you have free space but Docker still complains, look at df -i. An IUse% near 100% means you're out of inodes, not bytes — skip ahead to the inode section.

Step 2 — Safe cleanup
docker system prune
With no flags, this removes all stopped containers, networks not used by any container, dangling images, and unused build cache. It prompts before doing anything, and it leaves your tagged images, running containers, and all volumes alone.
Worth flagging because a lot of older posts get it wrong: modern docker system prune does clear build cache by default. If you read somewhere that you need a separate command for that, the article predates the current behavior. (Docker CLI reference)
For most "disk full" situations, this one command is the whole fix. Re-run docker system df afterward to confirm.
Step 3 — Aggressive cleanup
Still tight? Escalate. The -a flag widens the net from dangling images to all unused images:
docker system prune -a # all unused images + everything from the safe prune
docker image prune -a # just images, if that's all you want gone
docker builder prune -a # just build cache, drop ALL of it
"Unused" here means no container references the image. You'll re-pull or rebuild those on next use, so this trades disk now for a slower next build. On a build server, docker builder prune -a is frequently the single biggest win — that 22GB of cache in the example above goes in one shot.
Step 4 — Volumes (the danger zone)
⚠️ This is where data gets destroyed. Read before you run.
docker volume ls # look first — what volumes exist?
docker volume prune # removes anonymous volumes not used by any container
The --volumes flag on docker system prune only touches anonymous volumes — named volumes are preserved by design, exactly so you don't wipe a database by accident (reference). That's the safety net. Don't defeat it by manually deleting named volumes unless you are certain nothing needs the data. If a volume backs a Postgres or MySQL container, pruning it is gone-for-good.
This is the real reason not to reach for docker system prune -af --volumes reflexively: -f skips the confirmation prompt, and --volumes adds volume deletion. Together, in a hurry, that's how a quick cleanup becomes an incident.
Special case — out of inodes, not bytes
If df -i showed IUse% at or near 100%, free bytes won't save you. Every file and directory consumes one inode, and overlay2 (Docker's storage driver) creates an inode per file in every image layer. Image-heavy hosts — think Python images with a large site-packages, or anything with thousands of small files — burn through inodes long before they run out of space.
On ext4 the inode count is fixed when the filesystem is formatted and can't be grown later. So the practical fixes are:
- Prune images and containers (
docker system prune -a) — deleting files frees inodes, same command, different resource. - If pruning isn't enough, the filesystem itself needs reformatting with more inodes (
mkfs.ext4 -N) or Docker's data moved to a roomier filesystem (next section).
The tell is always df -i versus df -h disagreeing. (background)

Special case — Docker Desktop on Mac and Windows
If you're on Docker Desktop, the Linux advice above silently half-works, and here's why: Docker's data doesn't sit on your host filesystem directly. It lives inside a single VM disk image. Pruning frees space inside that disk — but the disk file on your host does not shrink automatically.
On Windows with the WSL2 backend, that file is a VHDX (ext4.vhdx, typically under C:\Users\<you>\AppData\Local\Docker\wsl\data). WSL2 allocates up to a 1 TB virtual disk by default and grows the file on demand as Docker writes to it — but deleting images never grows it back down. To reclaim host space after pruning:
# Quit Docker Desktop first, then in an admin PowerShell:
wsl --shutdown
Optimize-VHD -Path "C:\Users\<you>\AppData\Local\Docker\wsl\data\ext4.vhdx" -Mode Full
# or, on recent WSL builds:
wsl --manage <distro> --resize <size>
Docker Desktop also exposes a GUI path: Settings → Resources to cap the disk image size, and a Clean / Purge data button. (Hanselman's walkthrough, Microsoft Learn)
Permanent fix — move Docker's data root
If the disk keeps filling no matter how often you prune, the disk is just too small. Relocate /var/lib/docker to a bigger partition by setting data-root in /etc/docker/daemon.json:
sudo systemctl stop docker
sudo rsync -avxP /var/lib/docker/ /new/path/docker/
# edit /etc/docker/daemon.json:
# { "data-root": "/new/path/docker" }
sudo systemctl start docker
docker run hello-world # verify it works against the new location
# only after confirming: sudo rm -rf /var/lib/docker.old
The -x on rsync keeps it from crossing into other mounted filesystems, and -P lets you resume if it's interrupted. Verify with hello-world before you delete the old directory — not after. (guide)
Keep it from happening again
- Schedule a periodic
docker system prune -fon build servers (cron or a CI cleanup step). The-fis fine here precisely because it's unattended and you've decided what it removes. - Run throwaway containers with
--rmso they clean up on exit instead of accumulating stopped containers. - Shrink images at the source: multi-stage builds and smaller base images (
alpine,-slim, distroless) mean less to store and fewer inodes per image. - Watch
docker system dfas a habit, not just when something breaks.
Command reference
| Command | Removes | Data risk |
|---|---|---|
docker system df |
nothing (diagnostic) | none |
docker system prune |
stopped containers, unused networks, dangling images, build cache | low |
docker system prune -a |
+ all unused images | low (re-pull needed) |
docker image prune -a |
all unused images | low (re-pull needed) |
docker builder prune -a |
all build cache | low (rebuild needed) |
docker volume prune |
anonymous unused volumes | medium |
docker system prune -af --volumes |
everything above, no prompt | high — can delete data |
df -i /var/lib/docker |
nothing (inode check) | none |
Start at the top, stop when you have room, and only reach the bottom row when you know exactly what's in your volumes.



