How do you read `docker system df -v`, and why is its RECLAIMABLE column misleading?
answer
- Four buckets, then the itemised view
- Shared layers counted once, not per image
- UNIQUE SIZE predicts what deletion frees
- RECLAIMABLE assumes the aggressive prune
- Logs sit outside the report entirely
basics
~10 sdocker system df totals four buckets: images, containers, local volumes, build cache. -v itemises them with each image's SHARED and UNIQUE size. RECLAIMABLE assumes the aggressive sweep, not what a default prune returns.
solid answer
~40 s`docker system df` prints one row per object type — Images, Containers, Local Volumes, Build Cache — with TOTAL, ACTIVE, SIZE and RECLAIMABLE. `-v` expands each row into the individual objects. In the image table, SIZE is the whole image, SHARED SIZE is what it has in common with other images, and UNIQUE SIZE is the only part that deleting it actually frees; per-image sizes therefore sum to far more than the summary row, which counts each shared layer once. The container table's SIZE is the writable layer, not the image. The build cache table adds LAST USED, which is what age filters act on. The trap is RECLAIMABLE for images: it counts everything with no container attached, so it is the `prune -a` figure, not what a default `docker image prune` returns.
code
bash · 8 linesdocker system df
# itemised: SHARED/UNIQUE per image, writable layer per container,
# and LAST USED per build cache record
docker system df -v
# what the daemon's data root actually holds, for reconciliation
sudo du -sh /var/lib/docker/*go deeper
Know the command exists and what its four rows are: images, containers, local volumes, build cache. Be able to say that -v lists the individual objects instead of totals.
Explain SHARED versus UNIQUE size and why per-image sizes over-count, that the Containers row is the writable layer, and that RECLAIMABLE for images is the prune -a figure rather than the default prune's.
Demonstrate that you reconcile the report against du on the data root before acting, and that you know which consumers — container logs above all — are outside the report entirely.
Turn the report into a signal rather than a command someone runs during an incident: export the buckets as metrics, alert on trend and free space rather than absolute size, and decide which bucket the fleet is allowed to grow.
## The summary view `docker system df` is the daemon's own disk accounting, and it answers "where has the space gone" in four rows: - **Images** — layers of images stored locally. - **Containers** — the writable layer of each container, i.e. everything written inside a container that did not go into a volume or a bind mount. - **Local Volumes** — data in volumes managed by the daemon. - **Build Cache** — BuildKit's cache records. Each row carries TOTAL (how many objects), ACTIVE (how many are in use — for images, referenced by a container; for containers, running), SIZE, and RECLAIMABLE (size plus the percentage of that row's size the daemon considers recoverable). ## What `-v` adds, and the deduplication trap `docker system df -v` replaces the four-line summary with itemised tables. The image table is the interesting one because it has three size columns: - **SIZE** — the full size of that image, all of its layers. - **SHARED SIZE** — the part made of layers that at least one other local image also uses. - **UNIQUE SIZE** — the part nothing else uses. This is the only number that predicts what deleting the image frees. Say a Python inference image built on a 6.2 GB CUDA wheel base exists under eleven tags. Each row reports roughly 8.3 GB of SIZE, which sums to about 91 GB — but the summary Images row says 22.4 GB, because the shared 6.2 GB base is stored once and counted once. Deleting ten of those eleven tags reclaims ten times ~2.1 GB of UNIQUE SIZE, not 83 GB. Reading the itemised table without noticing the SHARED column is the single most common way engineers over-promise how much space a cleanup will return. The container table's SIZE column is likewise a specific thing: the container's writable layer only. A 40 GB row there is 40 GB of files the process wrote into the container filesystem — logs an application wrote to a path inside the container, a temp file that was never cleaned up, an accidental download — and it is freed only by removing the container, not by restarting it. `docker ps -s` shows the same figure per container without the rest of the report. The build cache table lists each cache record with its size, CREATED, LAST USED and USAGE count, and a shared flag. LAST USED is what `docker builder prune --filter until=<duration>` acts on, which is why a cache record from months ago that a build touched this morning survives an age-filtered prune. ## Why RECLAIMABLE is not a promise RECLAIMABLE is computed against the most aggressive prune available for that row, not the one you are about to run: - For **images**, it counts every image not referenced by any container. That is the `docker image prune -a` set. If you run the default `docker image prune`, you get only the dangling subset — often a tiny fraction of the advertised number. - For **containers**, it counts the writable layers of every non-running container, which `docker container prune` would remove along with the containers themselves. - For **local volumes**, it counts volumes with no container attached — and nothing prunes those unless you explicitly ask, because that is data. So the honest reading is: RECLAIMABLE is an upper bound achievable only by deleting everything not currently in use, which is a decision, not a cleanup. ## What the report does not see `docker system df` accounts for objects the daemon manages, so several real consumers are simply absent: - **Container log files.** With the default json-file driver the logs live in the daemon's data root but are not part of any row. A single chatty service with no rotation configured can be the largest thing on the disk and be invisible here. - **Bind mounts.** Host directories mounted into containers are the host's files; the daemon does not own or measure them. - **Transient daemon state**, such as the temporary files of a pull that is still in flight. That is why the useful second command is always `sudo du -sh /var/lib/docker/*` (or whatever the configured data root is): if `du` says 213 GB and `docker system df` accounts for 148 GB, the missing 65 GB is a real question, and it is usually logs. Reconciling the two views before you prune anything is what separates a targeted cleanup from a hopeful one.
- An image's row in `docker system df -v` shows SIZE 8.3 GB and UNIQUE SIZE 2.1 GB. How much disk does deleting it free?About 2.1 GB. The other 6.2 GB is in layers that at least one other local image also references, so those layers stay on disk until the last image using them is gone. UNIQUE SIZE is the only column that predicts a single deletion's yield; the summary Images row already counts shared layers once, which is why it is much smaller than the sum of the per-image SIZE column.
- Why can `du -sh` on the daemon's data root report far more than `docker system df` accounts for?Because the report only covers objects the daemon manages as images, containers, volumes and build cache. Container log files, temporary state from an in-flight pull, and anything a bind mount points at are outside those buckets. On a noisy service with no log rotation, the log files are frequently the biggest single item on the disk and never appear in the report at all.
- A container shows 41 GB in the SIZE column of `docker system df -v`. What frees that space?Removing the container. That column is the container's writable layer — files the process wrote inside the container filesystem rather than into a volume or bind mount. Stopping or restarting the container keeps the layer intact, and pruning images will not touch it because the layer belongs to the container, not the image. `docker container prune` reclaims it for stopped containers.
Reading per-image SIZE and adding it up is like summing the weight of every recipe's ingredient list in a kitchen: the one sack of flour that eleven recipes call for gets counted eleven times, but throwing out ten recipes does not remove ten sacks.
saying these in an interview costs you the question
- Adds up per-image SIZE and calls that total disk usage
- Treats RECLAIMABLE as what a default prune will free
- Thinks the Containers row measures their images
- Assumes `docker system df` counts container log files
- Believes restarting a container clears its writable layer
- Trusts df alone without comparing against du on the data root