If you use Docker on a Mac, storage bloat can sneak up on you fast. Images, build caches, stopped containers, and unused volumes can collectively consume 20–50 GB before you notice. Three commands address this — docker system prune, docker system prune -a, and docker system prune --volumes — but they differ enormously in how aggressively they clean. Understanding the docker system prune -a difference is essential before you run any of them, because some deletions are permanent and will force a full re-pull or re-build.
Where Docker Stores Its Data on macOS
On Apple Silicon and Intel Macs running macOS Sequoia or Tahoe, Docker Desktop stores all of its virtual machine state and image data inside a single large disk image rather than directly on your file system. The underlying VM disk lives at:
~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw
This file grows as you pull images and build layers, but it does not automatically shrink when you delete images inside Docker. That is one reason why the docker system prune family of commands matters: they do reclaim space inside the virtual disk, and Docker Desktop periodically compacts that file afterward. You can also trigger a manual reclaim from Docker Desktop → Settings → Resources → Disk → Clean / Purge data.
Beyond the VM image, Docker Desktop itself installs into /Applications/Docker.app and caches extension assets under ~/Library/Application Support/Docker Desktop/. Those paths are not touched by prune commands.
The Three Commands at a Glance
Here is a side-by-side comparison of what each command targets and what it leaves alone.
| Command | Stopped containers | Dangling images only | All unused images | Unused volumes | Build cache |
|---|---|---|---|---|---|
docker system prune |
Yes | Yes | No | No | Yes |
docker system prune -a |
Yes | Yes | Yes | No | Yes |
docker system prune --volumes |
Yes | Yes | No | Yes | Yes |
docker system prune -a --volumes |
Yes | Yes | Yes | Yes | Yes |
A dangling image is an image layer that has no tag and is not referenced by any container — the leftover from a rebuild. An unused image is any image not currently attached to a running or stopped container, including named images you pulled weeks ago.
docker system prune (Default): Safe but Conservative
docker system prune without flags is the most conservative option. It removes:
- All stopped containers
- All networks not used by at least one container
- All dangling (untagged, unreferenced) image layers
- The entire build cache
What it does not remove: named images that are not currently running, and any named volumes. If you pulled postgres:16 last month and no container is running it right now, the image survives. This makes the default prune relatively safe for day-to-day maintenance — your most recent dev images stay intact for the next docker compose up.
Typical space recovered: 1–5 GB, mostly from build cache churn on active projects.
docker system prune -a: The Deep Clean
Adding the -a (or --all) flag extends the sweep to every image that is not attached to a currently running container. This includes images you explicitly tagged, pulled, or built — not just dangling intermediates.
After running docker system prune -a:
- All stopped containers are gone.
- Every image not used by a live container is gone — including
node:20-alpine,nginx:latest, any base images you rely on for local builds. - The entire build cache is gone, so your next
docker buildstarts from scratch and will re-download base layers. - Volumes are still safe unless you also add
--volumes.
Typical space recovered: 10–40 GB on a machine that has been active for several months. The tradeoff is that your next project spin-up will be slow while Docker re-pulls base images.
Use -a before travel (where you want to reclaim disk), before a major project switch, or when your Docker VM disk image is near its size limit.
docker system prune --volumes: Adding Named Volumes
Named volumes are Docker-managed directories used to persist data across container restarts — database files, uploaded assets, config state. They live inside the Docker VM and are accessed via volume mounts like -v my_pgdata:/var/lib/postgresql/data.
Adding --volumes tells Docker to also delete every named volume not currently attached to a running container. This is the flag most likely to cause data loss. If your Postgres development database lives in a named volume and the container is stopped, it will be wiped.
When --volumes is safe to use:
- You are cleaning up after a project you no longer need.
- Your data is backed up externally or is purely throwaway dev seed data.
- You want a truly clean slate before switching Docker contexts.
When to avoid it: any time a stopped container holds database or application state you still need.
How to Prune Safely: Step-by-Step
- List what you have before deleting anything.
docker images— see all local images and their sizes.docker ps -a— see all containers including stopped ones.docker volume ls— see all named volumes.docker system df— shows a summary of disk usage broken down by images, containers, volumes, and build cache.
- Run a dry-run equivalent. Docker does not have a true dry-run, but you can use
docker system df -vto get verbose detail on what is reclaimable. - Start conservative. Run
docker system prunefirst (no flags). Note the space recovered. - Add
-aonly if you need more space and are willing to re-pull images on the next build. - Add
--volumesonly after confirming that every important volume is either backed up or intentionally discardable. You can export a volume withdocker run --rm -v my_volume:/data -v $(pwd):/backup alpine tar czf /backup/my_volume.tar.gz -C /data .before pruning. - After pruning, compact the VM disk in Docker Desktop → Settings → Resources → Disk if you want the
Docker.rawfile to shrink on disk.
How Much Space Can You Actually Recover?
Build cache tends to be the largest single category on machines doing active development. A six-month-old Mac used for web development might accumulate:
- Build cache: 8–20 GB (layer intermediates from Dockerfile iterations)
- Unused named images: 5–15 GB (old Node, Python, database base images)
- Stopped containers: negligible (usually under 100 MB total)
- Volumes: varies widely — can be 0 or can be 10+ GB for a database volume
If Docker storage is not your only concern, it is worth looking at other large developer caches as well. Xcode derived data, Gradle caches, and node_modules directories are common culprits alongside Docker — see our guide on what is taking up space on your Mac for a fuller picture. For node_modules specifically, our guide to cleaning up node_modules on Mac covers targeted strategies. A tool like Crumb can audit all of these at once and show what is safe before you delete.
Flags You Can Combine
The prune flags compose freely:
docker system prune -a --volumes— the nuclear option: removes everything not attached to a live container, including all images and all volumes. Prompts for confirmation.docker system prune -a --volumes -f— adds-f(force) to skip the confirmation prompt. Use only in scripts where you have already verified the state.docker builder prune— prunes only the build cache, leaving images, containers, and volumes alone. Useful when cache is the main offender.docker image prune -a— removes all unused images without touching containers, volumes, or build cache.
Running targeted sub-commands (docker builder prune, docker image prune) is often preferable to the all-in-one docker system prune because it gives you finer control over what gets removed.
Automating Cleanup Without Surprises
For machines used in CI or local development where you want regular cleanup without manual intervention, a simple approach is a weekly cron entry that runs the conservative prune:
docker system prune -f— clears stopped containers, dangling images, and build cache automatically.
Avoid automating -a or --volumes unless your workflow genuinely treats all local Docker state as ephemeral. Waking up to find that an overnight cron job deleted three months of Postgres dev data is an avoidable experience.