What does `docker image prune` delete by default, and what changes with `-a`?
answer
- Two modes, very different blast radius
- One mode only touches untagged images
- Even a stopped container protects its image
- -a means every image with no container
- until= and label= filters narrow the sweep
basics
~10 sdocker image prune deletes only dangling images: untagged layers orphaned when a tag moved to a newer build. Adding -a deletes every image that no container references, including tagged images you still want.
solid answer
~50 sA dangling image is one with no tag at all, listed as `<none>:<none>` by `docker images`. You create one every time you rebuild `checkout-api:latest`: the tag moves to the new image and the old one is left untagged. `docker image prune` removes exactly those and nothing else, prompting unless you add `-f`. `docker image prune -a` widens the target to every image not referenced by a container, **running or stopped**, so a perfectly good tagged image that nobody happens to be running right now is fair game. That is the entire difference, and it is why `-a` on a build host will happily delete the 6.2 GB CUDA base image of a Python inference build that you re-pull four minutes later. Both accept `--filter until=<duration|timestamp>` and `--filter label=...`, which is the sane way to run either from a timer.
code
bash · 8 lines# default: untagged, unreferenced images only
docker image prune -f
# every image no container references, but only if older than a week
docker image prune -a -f --filter "until=168h"
# spare anything the build labelled for retention
docker image prune -a -f --filter "until=168h" --filter "label!=retention"go deeper
Be ready to state the one-line difference: default prune takes only untagged images, -a takes every image with no container behind it. Know that docker images shows dangling images as <none>.
Explain how a dangling image is produced — a rebuild moving a tag off the old image — and that -a applies a container-reference test, not a usefulness test. Know the until= and label= filters and that they combine with AND.
Show the operational judgement: which prune you would run unattended on a mixed host, why a rollback image or a heavy base layer is the thing -a quietly destroys, and how removing old containers first changes what a later -a sweep deletes.
Own the policy question. Decide whether build hosts and run hosts get the same prune rules at all, and make images cheap to lose by treating the registry as the source of truth with immutable tags, so an over-eager sweep costs a pull rather than an incident.
## The two prune modes `docker image prune` has exactly two behaviours, and the whole question is about the blast radius of each. **Default (no flags): dangling images only.** Docker calls an image *dangling* when it carries no repository tag — `docker images` shows it as `<none>` in both the REPOSITORY and TAG columns. The overwhelmingly common way to make one is to rebuild: you build `checkout-api:latest`, the name `checkout-api:latest` is re-pointed at the new image ID, and the previous image keeps existing with the tag stripped off it. Its layers still occupy disk, nothing names them, and nothing will ever name them again unless you go find the image ID by hand. That is dead weight, which is why the default prune targets it and why the default prune is close to always safe. **`-a` / `--all`: every image no container references.** This drops the tag test entirely and applies a *usage* test instead: an image survives only if some container object on the host — running or stopped, healthy or dead — is based on it. Everything else goes, tags and all. On a machine whose job is to build, that is most of the local image library, because build outputs get pushed to a registry and are not running locally. ## Why "unused" is a trap word The mental model to fix is that `-a` does not mean "images I do not need"; it means "images with no container attached right now". Those are very different sets: - The image the previous release ran on, kept locally so a rollback is instant, has no container. `-a` deletes it. - A base image you build FROM twenty times a day has no container, ever. `-a` deletes it, and the next build re-pulls it. - A crashed container that exited three weeks ago still protects its image. `-a` keeps it. People routinely expect the opposite. The corollary is that `docker rm` of old containers can *unlock* images for a later `-a` sweep — the order in which you prune changes what disappears. ## Filters make `-a` usable Both modes take filters, and they are ANDed, so each extra `--filter` narrows the set further: - `--filter "until=168h"` — only images created more than a week ago. Accepts a Go duration (`24h`, `168h`) or a timestamp. - `--filter "label=project=inference"` — only images carrying that label, which you set with a `LABEL` instruction at build time. - `--filter "label!=retention"` — spare anything labelled, the standard way to pin a base image you never want re-pulled. `docker image prune -a -f --filter "until=168h"` is a defensible scheduled job. `docker image prune -a -f` with no filter, on a host that also runs services, is how a rollback image and a large base layer vanish at the worst moment. ## Where dangling images actually come from now On older engines the classic builder committed an intermediate image per instruction, so a day of building left dozens of `<none>` images behind and `docker image prune` was the daily broom. With BuildKit as the default builder (Docker Engine 23.0 and later for `docker build`), intermediate results are not images at all — they live in BuildKit's own content-addressed build cache. So on a modern host the `<none>` pile is much smaller and the space has moved: it is in the Build Cache row of `docker system df`, and it is `docker builder prune`, not `docker image prune`, that reclaims it. Candidates who learned prune on Docker 19 often prune images, see almost nothing freed, and conclude the command is broken. One genuinely untagged case is *not* garbage: an image referenced only as a member of a multi-platform manifest list you pulled. In practice on a normal host you rarely hit it, but it is the reason "untagged" and "unreferenced" are two different words. ## What prune never does It never shrinks an image — there is no such operation; an image is immutable and either exists or does not. It never touches a running container. It never removes volumes: `docker image prune` cannot delete data, which is exactly why it is the sweep you reach for first when a host is filling up and you are not yet sure what is safe.
- Does a container that exited weeks ago still protect its image from `docker image prune -a`?Yes. The test is whether any container object references the image, not whether it is running. A stopped, dead or created-but-never-started container all keep their image alive. That is why `docker container prune` first, then `docker image prune -a`, frees far more than the image sweep alone — and why an image you assumed was pinned can disappear once someone tidies up old containers.
- Why do you see far fewer dangling images on a recent Docker engine than you used to?Because BuildKit, the default builder since Docker Engine 23.0, does not commit an intermediate image per instruction the way the classic builder did. Intermediate results live in BuildKit's content-addressed build cache instead. The disk is still consumed, but it appears in the Build Cache row of `docker system df` and is reclaimed with `docker builder prune`, not `docker image prune`.
- How would you delete only the images produced by one project on a shared host?Label them at build time with a `LABEL` instruction, for example `LABEL project=inference`, then prune with `--filter "label=project=inference"`. Filters combine with AND, so adding `--filter "until=72h"` restricts it further to that project's older images. Labelling is also the way to do the inverse — `--filter "label!=retention"` spares anything explicitly pinned.
A dangling image is the old edition of a book after the title label was peeled off and stuck on the new printing: still on the shelf, taking space, and now impossible to ask for by name.
saying these in an interview costs you the question
- Says `docker image prune` removes all unused images by default
- Thinks only running containers protect an image from `-a`
- Believes prune shrinks images rather than deleting them
- Assumes every `<none>` image came from a failed build
- Puts `docker image prune -a -f` in cron on a host that also runs services
- Expects `docker image prune` to reclaim BuildKit build cache