Docker Disk Usage and Cleanup
Where the space actually goes - measured, not guessed. On this host the build cache was the biggest reclaimable item and dangling images were zero, which is the opposite of the usual advice.
Operations and Troubleshooting Guide 32 of 46 Intermediate
- OSUbuntu 26.04 LTS (resolute)
- Docker Engine29.7.2
- cgroupv2, systemd driver
- Architectureamd64
- TimeAbout 13 min
- Reviewed22 August 2026
Tested on the versions above. Every size here is from one real host. Yours will differ - the point is the method and the ordering, not the numbers.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| DOCKER01 | 192.168.0.21 | Ubuntu 26.04 LTS | Docker Host | 2 Core | 4 GB | 50 GB |
Before you start
- You can list images, containers and volumes - guide 6 in this path.
- An understanding of layers - guide 17 in this path.
-
Account before you delete
docker system dfbreaks usage into four categories and tells you how much of each is reclaimable. Run this first, always. Deleting before measuring is how people remove an image they needed and keep the 400 MB of build cache they did not.bash Example session docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 9 0 819.7MB 806.7MB (98%)Containers 0 0 0B 0BLocal Volumes 1 0 0B 0BBuild Cache 45 0 306.6MB 254.3MBExpected resultFour rows with a reclaimable column.
Success conditionYou can name the biggest reclaimable category before touching anything. Note ACTIVE 0 - nothing was running, so everything here is idle, not in use.
-
Watch a build move the numbers
Building one image with a 40 MB layer, then rebuilding it with slightly different content, changes two categories at once. Images grew as expected - but look at the build cache, which grew by more than the image did.
bash Example session docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 10 0 905.7MB 880MB (97%)Build Cache 49 0 476.5MB 338.2MBExpected resultImages up by roughly one image, and build cache up by more.
Success conditionYou have seen the build cache outgrow the artefact it produced. On a CI worker this is usually the number one consumer.
-
The dangling-image assumption is often wrong
The standard advice is that rebuilding leaves
images behind that you should prune. On this host, with BuildKit, rebuilding and retagging produced NO dangling images at all - the filter returns nothing and the prune reclaims zero bytes. Check before you believe the folklore, on your own host and your own builder.bash docker images --filter dangling=true --filter label=cg-demo=1 --format "{{.ID}} {{.Size}}"# no output - no dangling images were createddocker image prune -f --filter label=cg-demo=1Total reclaimed space: 0BExpected resultNothing dangling, and zero bytes reclaimed.
Success conditionYou measured instead of assuming. Where the space went was the build cache, which
docker image prunedoes not touch at all. -
Container size does not include volume data
docker ps --sizereports the writable layer and the virtual size. A container that wrote 20 MB into a mounted volume still shows a tiny writable layer, because volume data is not part of the container. Volumes are counted separately insystem df, and they are the category most often overlooked.bash Example session docker ps --size --filter name=cg-disk-c --format "table {{.Names}}\t{{.Size}}"NAMES SIZEcg-disk-c 8.19kB (virtual 8.99MB)Expected resultA writable layer of a few kilobytes despite the container writing 20 MB.
Success conditionYou know why a "small" container can still be responsible for gigabytes. Use
docker system df -vto attribute volume space. -
Delete by name first, prune second
Named removal is precise and reversible in the sense that you know exactly what went. Prune is a blunt instrument that removes everything matching a category, including things another person on the host still needs. Prefer names, and when you must prune, scope it with a filter.
bash docker rm -f cg-disk-c && docker volume rm cg-disk-v && docker rmi cg-disk:1cg-disk-ccg-disk-vUntagged: cg-disk:1Deleted: sha256:0bfa7cbe837fc6bd5fe6399029a4580b84248d35130bc778913d050739e6362bExpected resultImages back to the count and size you started with.
Verify it worked
bash Example session docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLEImages 9 0 819.7MB 806.7MB (98%)Success conditionThe accounting returned to baseline. That is what a clean-up should look like - a number you can check, not a hope.
-
The prune commands, in order of danger
Know what each one takes before you run it.
docker container pruneremoves stopped containers.docker image pruneremoves dangling images; with-ait removes every image not used by a container, which on a shared machine will delete someone else's work.docker volume pruneremoves unused volumes and that is DATA.docker builder pruneremoves build cache and is usually the one that actually frees the space.bash # all of these are irreversible - none of them prompt for anything except a yes/nodocker container prune # stopped containersdocker builder prune # build cache - usually the big windocker image prune -a # EVERY unused image, not just danglingdocker volume prune # DELETES DATAdocker system df# run this first, and again after, so you can prove what you reclaimedExpected resultYou reading the list rather than running them all.
Success conditionYou can say which command would have freed the most on the host above -
docker builder prune, at 424 MB reclaimable, not any image command. -
Scope a prune with a filter
Filters turn a blunt command into a precise one.
label=is the strongest: label everything a build or a CI job creates, then prune only that label.until=is the common time-based scope. On a machine you share, an unfiltered prune is not yours to run.bash docker image prune -f --filter label=cg-demo=1Total reclaimed space: 0B# only images carrying that label were considereddocker builder prune --filter until=168h # cache older than a weekExpected resultA prune that reports what it reclaimed, having considered only the labelled subset.
Success conditionYou have a cleanup you could safely put in a cron job. An unfiltered one you could not.
Troubleshooting
no space left on device
Why: The filesystem holding /var/lib/docker is full. Common causes in order: build cache, unbounded container logs, unused images, orphaned volumes.
Fix:Measure with
system dffirst, then take the biggest reclaimable category. Check logs separately - they are not insystem dfat all.bash df -h /var/lib/dockerdocker system dfsudo du -sh /var/lib/docker/containers/* | sort -h | tail -5Pruning freed far less than system df promised
Why: Reclaimable counts each category independently, and layers shared between images are only freed once nothing references them.
Fix:Re-run
system dfafter each prune rather than adding the numbers up in advance.bash docker system dfA volume you needed was deleted
Why:
docker volume pruneremoves every volume not attached to a container, which includes ones deliberately left detached between deployments.Fix:There is no undo. Back up volumes you care about - see guide 13 - and never prune volumes on a shared host.
bash # check what would go BEFORE pruning: anything with LINKS 0docker system df -v | grep -A20 "Local Volumes"Build cache grows back immediately
Why: It is doing its job - the next build repopulates it. Pruning the cache trades disk for build time.
Fix:Bound it with
--filter until=on a schedule rather than emptying it, or use--keep-storagewhere your version supports it.bash docker builder prune --filter until=168h