Skip to main content
RunBook Academy

Docker & ContainersXXVIII Β· MaintenanceReclaiming disk

Prune semantics β€” exactly what each variant deletes

Intermediate⏱ ~24 mindocker

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

Not yet marked complete on this device.

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

Destructivesystem prune
$ docker system prune
WARNING! 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:

  1. All stopped containers. Every container not currently running, including containers in the created and exited states. A container you stopped an hour ago to investigate something is gone, along with its writable layer.
  2. All networks not used by at least one container. User-defined networks with nothing attached. The built-in bridge, host and none networks are never removed.
  3. All dangling images. A dangling image is one with no tag and no container referencing it β€” the <none>:<none> rows in docker images. These are almost always superseded build outputs.
  4. 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

Data-loss risksystem prune -a --volumes
$ docker system prune -a --volumes
WARNING! 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 kindCreated byRemoved by --volumesRemoved by docker volume pruneRemoved by docker volume prune -a
Anonymousdocker run on an image with a VOLUME instruction, or -v /path with no nameYes, if unattachedYes, if unattachedYes, if unattached
Nameddocker volume create, -v myvol:/path, Compose volumes:NoNoYes, 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.

Destructivetargeted prunes
# 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 prune

Every 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

Destructiveage filter
# 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:

Configuration changeretention label
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.

Read-only / Safepre-prune inventory
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…RunNot
Clear failed build leftoversdocker image prunedocker image prune -a
Free space on a build hostdocker builder prune --filter 'until=168h'docker system prune -a
Remove containers from a failed deploydocker container prune --filter 'until=24h'docker container prune
Reclaim orphaned anonymous volumesdocker volume prunedocker volume prune -a
Reset a scratch host completelydocker system prune -a --volumesanything, 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

  1. Q1. Which images does a plain `docker system prune` (no flags) remove?

  2. 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`?

  3. Q3. Which statements about `docker system prune -a` are true? Select all that apply.

  4. 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.