Docker & ContainersXXVIII Β· MaintenanceReclaiming disk
Prune semantics β exactly what each variant deletes
What you'll learn
- State exactly what `docker system prune` removes with and without each flag
- Predict which images survive a prune and which do not
- Use per-object prune commands instead of the aggregate one
- Apply `until` and `label` filters to bound a prune
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
docker system prune is the most-run maintenance command in Docker and the
least-read confirmation prompt in operations. It has one default behaviour and
two flags, and each flag widens the blast radius in a way that is not obvious
from its name.
Read this lesson before you run it on anything you care about.
The default
$ docker system pruneWARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- unused build cache
Are you sure you want to continue? [y/N]Four things, and the wording is precise:
- All stopped containers. Every container not currently running,
including containers in the
createdandexitedstates. A container you stopped an hour ago to investigate something is gone, along with its writable layer. - All networks not used by at least one container. User-defined networks
with nothing attached. The built-in
bridge,hostandnonenetworks are never removed. - All dangling images. A dangling image is one with no tag and no
container referencing it β the
<none>:<none>rows indocker images. These are almost always superseded build outputs. - Unused build cache. BuildKit cache records not currently referenced.
Note what is not in the list: tagged images, and volumes of any kind.
-a β the flag that changes the meaning
$ docker system prune -a --volumesWARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all anonymous volumes not used by at least one container
- all images without at least one container associated to them
- all build cache
Are you sure you want to continue? [y/N]Two lines changed, and they are the two that matter.
βall dangling imagesβ became βall images without at least one container associated to themβ. That is every image on the host that no container β running or stopped β currently references. Tagged or not. Pulled five minutes ago or not.
βunused build cacheβ became βall build cacheβ. Not the unreferenced part: all of it. The next build starts from nothing.
--volumes β narrower than it sounds
--volumes adds anonymous volumes not used by at least one container. It
does not touch named volumes. That has been the behaviour since API 1.42
(Docker 23.0); before that, the flag took named volumes too, which is why
older runbooks and older instincts are dangerous here.
The distinction:
| Volume kind | Created by | Removed by --volumes | Removed by docker volume prune | Removed by docker volume prune -a |
|---|---|---|---|---|
| Anonymous | docker run on an image with a VOLUME instruction, or -v /path with no name | Yes, if unattached | Yes, if unattached | Yes, if unattached |
| Named | docker volume create, -v myvol:/path, Compose volumes: | No | No | Yes, if unattached |
So the genuinely dangerous volume command is not docker system prune --volumes. It is docker volume prune -a, which will delete an unattached
named volume β the exact shape of a database volume whose container you
stopped for maintenance.
Prefer the per-object commands
docker system prune is a convenience wrapper around four separate commands.
On a production host, run the specific one instead. You get a narrower blast
radius and a clearer change record.
# Stopped and created containers. No images, no volumes.
docker container prune
# Untagged, unreferenced images only.
docker image prune
# Every unreferenced image, tagged or not. The dangerous one.
docker image prune -a
# User-defined networks with nothing attached.
docker network prune
# Unattached ANONYMOUS volumes only.
docker volume prune
# Unattached volumes including NAMED ones. Deletes data.
docker volume prune -a
# Unreferenced build cache.
docker builder pruneEvery one of these accepts -f to skip the confirmation prompt. Use -f in
automation only after you have bounded the command with a filter, never as a
way to stop being asked.
Bounding a prune with filters
Two filters do most of the useful work.
until
# Images created more than 30 days ago
docker image prune -a --filter 'until=720h'
# Containers that exited before an absolute timestamp
docker container prune --filter 'until=2026-07-01T00:00:00'
# Build cache older than a week
docker builder prune --filter 'until=168h'label
The filter that actually expresses intent. Label the images you want kept, and exclude them:
docker build --label 'retain=true' -t myapp:release-2026-08 .
# Prune everything unreferenced EXCEPT images carrying that label
docker image prune -a --filter 'label!=retain=true'label!=key=value excludes matching objects. Multiple filters with different
keys combine with AND; multiple filters with the same key combine with OR.
Making a prune reversible
You cannot undo a prune. You can make the state before it recoverable, and that takes about thirty seconds.
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
mkdir -p "/var/backups/docker-inventory/$STAMP"
# Every image with its digest β this is what lets you re-pull exactly
docker image ls --digests --format '{{.Repository}}:{{.Tag}} {{.Digest}}' > "/var/backups/docker-inventory/$STAMP/images.txt"
# Every volume
docker volume ls --format '{{.Name}} {{.Driver}}' > "/var/backups/docker-inventory/$STAMP/volumes.txt"
# Every container spec, running or not
docker ps -a --format '{{.Names}} {{.Image}} {{.Status}}' > "/var/backups/docker-inventory/$STAMP/containers.txt"The images file is the valuable one. With repository, tag and digest recorded, a wrong prune becomes a re-pull rather than a rebuild β provided the digests still exist in a registry, which is the assumption worth checking before you rely on it.
The decision table
| You want to⦠| Run | Not |
|---|---|---|
| Clear failed build leftovers | docker image prune | docker image prune -a |
| Free space on a build host | docker builder prune --filter 'until=168h' | docker system prune -a |
| Remove containers from a failed deploy | docker container prune --filter 'until=24h' | docker container prune |
| Reclaim orphaned anonymous volumes | docker volume prune | docker volume prune -a |
| Reset a scratch host completely | docker system prune -a --volumes | anything, on a host that is not scratch |
The last row is the honest one. docker system prune -a --volumes is a
correct command β for a CI runner or a lab host you are about to reuse. It has
no place in routine maintenance of a host running services.
Knowledge check
Knowledge check Β· 4 questions
Q1. Which images does a plain `docker system prune` (no flags) remove?
Q2. A host runs a Compose stack with a named volume `pgdata`. Every container is currently stopped for maintenance and the containers still exist. Which command deletes the contents of `pgdata`?
Q3. Which statements about `docker system prune -a` are true? Select all that apply.
Q4. The `until=720h` filter on `docker image prune` removes images that have not been used in 30 days.
Passing score: 75%. Answers are checked in this browser.