Docker & ContainersXXVIII Β· MaintenanceDisk accounting
Where the disk went β reading `docker system df` correctly
What you'll learn
- Interpret every column of `docker system df` and `docker system df -v`
- Explain why the sum of image sizes exceeds the disk they occupy
- Name the disk consumers that `docker system df` does not account for
- Locate the real consumer before running any prune command
Prerequisites
Verified against Docker Engine 29.x Β· Docker Engine 28.x Β· Docker Compose 2.x Β· containerd 2.x Β· runc 1.2.x Β· BuildKit 0.20+ Β· Linux kernel 5.15+ Β· Ubuntu 24.04 LTS Β· Debian 12 (Bookworm) Β· 2026-08-11
Every Docker maintenance task begins with the same question: what is eating the disk? The wrong answer sends you to a prune command that reclaims two hundred megabytes while the actual consumer keeps growing.
This lesson is about measuring first. The commands are all read-only. Nothing here deletes anything β that is the next lesson, deliberately.
The four categories
$ docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 34 10 17.98GB 14.46GB (80%)
Containers 10 9 192.5kB 49.15kB (25%)
Local Volumes 47 8 2.901GB 1.684GB (58%)
Build Cache 87 0 7.446GB 5.124GBIllustrative output
Four categories, and each one is stored somewhere different underneath
/var/lib/docker:
| Category | Where it lives | What it is |
|---|---|---|
| Images | overlay2/ plus image/ metadata | Read-only layers pulled or built |
| Containers | overlay2/ writable layers | Per-container changes since start |
| Local Volumes | volumes/ | Named and anonymous volume contents |
| Build Cache | buildkit/ | BuildKitβs content-addressed cache |
The host above has almost 18 GB of images and 7.4 GB of build cache against 193 kB of container writable layers. That ratio is typical and it is the first thing to internalise: on a normal production host, containers are almost never the problem. Images and build cache are.
What ACTIVE means, per category
ACTIVE does not mean βin use right nowβ. It means something slightly
different in each row, and the differences are where operators go wrong.
- Images. Active means referenced by at least one container β running or stopped. An image used only by a container you stopped last week still counts as active.
- Containers. Active means running. Stopped and
createdcontainers are counted inTOTALbut not inACTIVE. - Local Volumes. Active means attached to at least one container, again running or stopped.
- Build Cache. Active is effectively always
0in the summary; build cache records are not held by containers.
RECLAIMABLE is an upper bound with a footnote
The RECLAIMABLE figure for images is what docker image prune -a would
free β every image not referenced by a container. The percentage in brackets
is that number over SIZE.
For volumes, RECLAIMABLE counts every unattached volume, including named
ones. But the default docker volume prune only removes anonymous volumes.
So the volume row routinely reports more reclaimable space than the default
prune will actually reclaim. That gap is a feature: it is the difference
between βspace that is not referencedβ and βspace it is safe to deleteβ.
The verbose view is the one that finds the culprit
docker system df -vThe images section gains three columns the summary does not have:
$ docker system df -vImages space usage:
REPOSITORY TAG IMAGE ID SIZE SHARED SIZE UNIQUE SIZE CONTAINERS
myapp dev 0ef9d88b4b73 1.05GB 384.8MB 669.4MB 0
myapp release-2026-07 bea532850da8 964MB 424.8MB 539.3MB 0
postgres 16.14-bookworm 92620daddcd9 622MB 0B 621.8MB 1
python 3.12-slim 6c4dd321d176 179MB 176.9MB 2.179MB 0
nginx 1.27-alpine 97d490c12ba5 93.5MB 12.92MB 80.58MB 0Illustrative output
- SIZE is the logical size of the image: every layer added up.
- SHARED SIZE is the part of that made of layers other images also use.
- UNIQUE SIZE is what deleting this image would actually free.
- CONTAINERS is the reference count that drives
ACTIVE.
Look at python:3.12-slim: 179 MB of size, 176.9 MB of it shared, 2.2 MB
unique. Deleting it frees 2 MB. Meanwhile myapp:dev has 669 MB of unique
layers and no containers β that one is worth 669 MB.
Why the numbers do not add up to du
Compare the two:
docker system df
sudo du -sh /var/lib/docker
df -h /var/lib/dockerThey will disagree, in both directions, for reasons worth knowing:
What docker system df does not show
This is the part that costs people an outage.
Container logs
The Containers row counts the writable layer only. Log files live at
/var/lib/docker/containers/<id>/ β outside the layer β so they are invisible
to docker system df.
$ docker ps -s --format 'table {{.Names}}\t{{.Size}}'NAMES SIZE
grafana 4.1kB (virtual 576MB)
prometheus 20.5kB (virtual 297MB)
postgres 20.5kB (virtual 461MB)Illustrative output
Those containers have been up for weeks. Their reported size is kilobytes.
The JSON log for a chatty container next to them can be gigabytes, and no
Docker df column will ever mention it. Find those separately:
sudo du -sh /var/lib/docker/containers/* | sort -h | tail -10Rotation is the fix, and it is covered in the logging part of this course. What matters here is knowing the number is missing from your measurement.
Bind mounts
A bind mount is a host path. It is not a Docker object, so it appears in no
Docker accounting anywhere. A container writing 40 GB into /srv/appdata
shows up only in df -h.
The containerd content store
Pulled image content is also held by containerd under
/var/lib/containerd. On a host that has been running a long time this is
usually modest, but it is a separate tree with a separate lifecycle and it is
not in the four categories.
A measurement routine
Run this before any prune, every time. It takes ten seconds and it is the difference between a targeted cleanup and a guess.
# 1. Filesystem reality
df -h /var/lib/docker
# 2. Docker's four categories
docker system df
# 3. Per-object detail for whichever category dominates
docker system df -v
# 4. The blind spot: container logs
sudo du -sh /var/lib/docker/containers/* | sort -h | tail -5
# 5. The other blind spot: bind mount targets
docker ps --format '{{.Names}}' | xargs -r -I{} docker inspect -f '{{.Name}} {{range .Mounts}}{{.Source}} {{end}}' {}Step 3 is the one that decides what you do next. If images dominate, you have an image retention problem. If build cache dominates, you have a build host without a cache policy. If volumes dominate, you have orphans. Each of those is a different lesson in this part, and each has a different safe procedure.
Knowledge check
Knowledge check Β· 4 questions
Q1. Which column of `docker system df -v` predicts how much disk space deleting a specific image would actually free?
Q2. A container has been running for three weeks and writes heavily to stdout. What does the Containers row of `docker system df` report for it?
Q3. Which of these consume host disk but are NOT accounted for anywhere in `docker system df`? Select all that apply.
Q4. An image referenced only by a container that is stopped still counts as ACTIVE in the Images row of `docker system df`.
Passing score: 75%. Answers are checked in this browser.