Quick answer

docker system prune --volumes removes ANONYMOUS volumes only — Docker’s own flag description says so, and the confirmation prompt repeats it. A named volume, which is what any sane compose file gives a database, is out of scope. The command that can delete named volumes is docker volume prune --all, and it sounds narrower than the one everybody fears.

By LK Wood IV · 2026-08-15 · ~8 min read · St. Louis County, MO

The received wisdom is simple. docker system prune -a --volumes eats databases, so never run it.

That fear is aimed at the wrong command. Docker’s own options table describes --volumes as “Prune anonymous volumes”, and the confirmation prompt spells out what it will remove: all anonymous volumes not used by at least one container. A named volume is not anonymous. If your Postgres data lives in a volume you named in a compose file, docker system prune -a --volumes is not the thing that will take it.

The command that removes named volumes is this one.

docker volume prune --all

Docker documents that by default docker volume prune only removes anonymous volumes, and that --all removes all unused volumes, not just anonymous ones, behind an API 1.42 gate. Read those two sentences next to each other and the asymmetry is obvious: the destructive option is a four-character flag on a command whose name sounds like it only touches leftovers.

So the scary-sounding command is narrower than its reputation. The mild-sounding one is the loaded gun. That inversion — feared command safe, safe-sounding command dangerous — is the whole reason this page exists.

One caveat before you act on any of it. Everything above is read from Docker’s current documentation, not from a test I ran on this machine. I have no Docker daemon here. The optional demonstration at the end requires an entirely disposable daemon. Inspect mounts on your real host without running a prune as a test.

Look before you sweep

docker system df

That breaks usage into images, containers, local volumes and build cache, with a reclaimable column. Run it first. Every time.

I push this because “reclaim disk space” is usually the wrong framing, and running a prune before you have looked at docker system df is how people end up re-pulling forty gigabytes of images to solve a problem that was one container writing an unbounded log file the whole time. The question is what filled the disk, and the answer changes the fix entirely. Build cache on a machine that builds often is a different problem from forty pulled images you never run, which is different again from one container writing an unbounded log. Only one of those is solved by pruning.

What each command removes

docker system prune, per its confirmation prompt, removes exactly four things:

  • all stopped containers
  • all networks not used by at least one container
  • all dangling images
  • unused build cache

No volumes at all. A dangling image — one that no tag points at any more — is typically what is left after rebuilding under the same tag.

docker system prune -a adds, per the options table: Remove all unused images not just dangling ones.

This is a much wider sweep than it reads. “Unused” means no container is associated with the image, tagged or not. On a homelab where you keep images around for things you run occasionally, -a removes them and you will pull them again over your home connection at the least convenient moment. It is safe for your data. It is expensive for your time.

docker system prune --volumes adds anonymous volumes, as covered above.

docker volume prune takes anonymous volumes only, by default.

docker volume prune --all is the one to watch. Unused named volumes go too.

docker builder prune takes build cache only. It is the safest command here, because build cache is reproducible by definition. On a machine that builds regularly it is also often the entire problem.

The trap that is genuinely real

Here is the case where you can genuinely lose data to anonymous-volume pruning. It is not the one people worry about.

Some official images declare a VOLUME in their Dockerfile. Postgres declares one at its data directory. When you start such an image without giving it a named volume or a bind mount, Docker satisfies that declaration by creating an anonymous volume. It has a long hex name you never chose. It holds your actual database.

# creates an anonymous volume holding real data
docker run -d postgres:17

# creates a named volume you control
docker run -d -v pgdata:/var/lib/postgresql/data postgres:17

Stop that first container and the anonymous volume is now unused, and squarely in scope for --volumes.

So the failure mode is not “prune ate my named volume”. It is “I never named the volume, so Docker quietly named it for me, that autogenerated name made it anonymous by definition, and anonymous is precisely the category the flag I ran was documented to remove”. The fix is upstream of any prune command: name your volumes. A compose file with a top-level volumes: block gets this right by default, which is one more reason to run things through compose rather than long docker run lines.

What I run weekly

Weekly, on a homelab Docker host, with the reasoning attached:

docker system df          # look first
docker builder prune      # cache is reproducible
docker system prune       # stopped containers, unused networks, dangling images

Before the last command, save anything needed from stopped containers: pruning removes their writable layers too. A stopped container can still hold your only copy of a file.

I add -a only when I have decided I am willing to re-pull, which on a metered or slow connection is a real cost rather than a theoretical one.

I never run docker volume prune --all on a schedule. A volume is the one thing here that might be the only copy. When I want a specific volume gone, I look at it first and then remove it by name, which is slower in exactly the way that a command capable of destroying the only copy of something ought to be:

docker volume ls
docker volume inspect <name>
docker volume rm <name>

Slower. It also makes me say what I mean.

Optional demonstration on a disposable daemon

docker system prune acts across its target daemon. Creating two test volumes does not restrict it to those volumes. On your real host, inspect docker system df -v and the relevant container mounts; do not run a global prune just to test what it deletes.

For the optional demonstration, use a separate empty Linux VM whose entire Docker storage you can lose. Create a Docker context named prune-lab pointing only to that VM, following Docker’s context documentation. Inspect that context’s endpoint and confirm it is the disposable VM before proceeding. A context name alone proves nothing about its target.

# Read these results before continuing. The endpoint MUST be the disposable VM.
docker context inspect prune-lab
docker --context prune-lab info --format '{{.Name}}'
docker --context prune-lab ps -a
docker --context prune-lab volume ls

Stop if there is any real workload or data on that daemon. Every command in the experiment names the context explicitly. These containers are created but never started; no database initialization or password is needed.

# ONLY after verifying the separate, disposable daemon above.
docker --context prune-lab pull alpine
docker --context prune-lab volume create keepme
docker --context prune-lab create --name anon-test -v /data alpine
docker --context prune-lab create --name named-test -v keepme:/data alpine
docker --context prune-lab rm anon-test named-test
docker --context prune-lab volume ls
docker --context prune-lab system prune -a --volumes
docker --context prune-lab volume ls

The documented prune behavior predicts that the unused anonymous volume disappears and keepme remains. Compare the before and after lists. This experiment can delete resources across the whole disposable daemon. Keep it off your active host.

What I have not tested

I have not run any of this on a live daemon during this write-up, for the reason given at the top. Every behavioural claim here traces to Docker’s current CLI documentation.

I have not checked how an older daemon behaves, and this is the gap I would most want closed before anyone runs these commands on a long-lived box that has not been updated in a couple of years. --all on docker volume prune carries an API 1.42 gate, which implies the pre-1.42 behaviour differed, and I have not established what a new CLI does against an old daemon. If you are on something long-lived and unpatched, test rather than assume.

I have not measured how much any of this reclaims in practice. That number is a property of your machine, and quoting mine would tell you nothing.

What getting it wrong costs

Pruning images costs you a download. Pruning build cache costs one slow build. Both are annoyances with a known price.

Pruning a volume costs you whatever was in it, and the price is only discovered later, usually when something tries to read data that is no longer there and fails in a way that does not obviously say “your volume is gone”. If that data mattered, the real lesson is not about prune flags at all. It is that a volume you would miss should be backed up, because a wrong flag is only one of many ways to lose it.

Frequently asked questions

Does docker system prune -a --volumes delete my database volumes?
Not if the database uses a named volume. Docker’s options table describes --volumes as ‘Prune anonymous volumes’, and the confirmation prompt says it removes all anonymous volumes not used by at least one container. Named volumes are not anonymous. This is documented behaviour rather than a bench result on my part, so inspect your mounts and keep a tested backup. The optional prune demonstration requires a separate disposable Docker daemon; test volumes do not limit a global prune.
What does docker system prune remove by default?
The confirmation prompt lists four things: all stopped containers, all networks not used by at least one container, all dangling images, and unused build cache. It does not touch volumes at all unless you add --volumes.
What does the -a flag add?
Docker’s options table describes it as ‘Remove all unused images not just dangling ones’. That is a much bigger sweep than it sounds. A dangling image is one no tag points at; an unused image is any image with no container associated with it, tagged or not. On a homelab where you pull images and run them only occasionally, -a will remove things you will have to pull again.
Which command deletes named volumes?
docker volume prune --all. Docker documents that by default docker volume prune only removes anonymous volumes, and that --all removes all unused volumes, not just anonymous ones. The --all flag is gated at API 1.42 and above. This is the command to be careful with, and it is worded more mildly than the one people are usually afraid of.
Why did a volume disappear when I never created one?
Some official images declare a VOLUME in their Dockerfile — Postgres declares one at its data directory. Run such an image without giving it a named volume or bind mount and Docker creates an ANONYMOUS volume to satisfy the declaration. That volume holds real data and is in scope for anonymous-volume pruning. The fix is to always name the volume yourself.
How do I see what is using the space first?
docker system df. It breaks usage into images, containers, local volumes and build cache, with a reclaimable column. Run it before any prune so you know whether your problem is images, cache, or one runaway log — pruning is a poor substitute for knowing.
How do I clear just the build cache?
docker builder prune. On machines that build often this is frequently the entire problem, and it is the safest of these commands because build cache is by definition reproducible.

Sources and corrections

Last updated
Methodology
See our methodology for research and review standards.
Update log
  • 2026-09-08 — Replaced the scratch-volume test that ran global prune against the active daemon. The optional experiment now requires an independently verified disposable VM endpoint and uses –context prune-lab on every workload/prune command; created containers do not require PostgreSQL startup. Removed the implication that stopped-container data is always rebuildable and updated the FAQ/caveat. Docker context and pruning documentation checked 2026-09-08. No Docker daemon was started or pruned.
Corrections
Spotted an error or a stale number? Email contact@techfuelhq.com. Confirmed corrections are added to the update log above.

About the author

Written by Lowell K. Wood IV, who builds and runs TechFuelHQ from St. Louis, Missouri.