DEV Community

Sarmad Hussain
Sarmad Hussain

Posted on Originally published at dustystorage.com

Docker Desktop is Secretly Hoarding 30GB — Here's How to Reclaim It

You open System Settings → General → Storage on your Mac and see the dreaded label: "System Data: 98 GB". You haven't downloaded movies, and your Downloads folder is empty. Yet your 512GB SSD is choking.

If you are a developer on macOS, the primary suspect is almost certainly Docker Desktop.

Even with just a few containers or a local Postgres instance, Docker on macOS silently hoards 30GB to 80GB of disk space. Worse: when you delete containers or images inside Docker, macOS doesn't automatically get that disk space back.

In this guide, we'll dissect Docker Desktop's storage architecture on macOS, explain why the virtual disk never shrinks on its own, and provide Terminal commands to reclaim your space.


1. Why Docker on macOS is Fundamentally Different

On Linux, Docker runs natively against the host kernel using cgroups and overlay filesystems (overlay2). Files write directly to /var/lib/docker on your host drive.

macOS, however, uses the Darwin kernel with no native Linux container primitives. To run containers, Docker Desktop spins up a lightweight Linux virtual machine via Apple's Hypervisor framework.

┌────────────────────────────────────────────────────────┐
│ macOS Host (APFS Filesystem)                           │
│  ~/Library/Containers/com.docker.docker/Data/vms/...   │
│  └── 📁 Docker.raw (Virtual Disk Image: 64 GB)        │
│        ▲                                               │
│        ▼ [Block-level virtualized disk]                │
│  ┌──────────────────────────────────────────────────┐  │
│  │ Linux VM (ext4 Filesystem)                       │  │
│  │  /var/lib/docker/ (overlay2, buildkit, volumes)  │  │
│  └──────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Everything inside Docker—container layers, pulled images, BuildKit caches, and volume mounts—is packed into a single virtual disk file on your host drive named Docker.raw (or Docker.qcow2).


2. Locating Docker.raw on APFS

The virtual disk image is typically located at:

~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw
Enter fullscreen mode Exit fullscreen mode

The APFS Sparse File Trap: Apparent vs. Physical Size

If you check the file with ls -lh:

ls -lh ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw
Enter fullscreen mode Exit fullscreen mode

You might see 64G. However, Docker.raw is an APFS sparse file; it only allocates physical blocks on your SSD as data is written. To see how much physical space it actually consumes, use du:

du -sh ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw
Enter fullscreen mode Exit fullscreen mode

If du -sh reports 38G, Docker is actively taking 38 GB of physical flash storage.


3. Why Docker's Virtual Disk Never Shrinks

Here is the fundamental architectural trap with docker mac storage:

  1. When you run docker build or pull an image, Linux writes blocks into the virtual disk.
  2. The APFS host sees new blocks written and expands the physical footprint of Docker.raw.
  3. Later, you run docker rm or delete an old project. The Linux VM marks those sectors as free within its ext4 filesystem.
  4. However, APFS on macOS has no automatic way of knowing that those blocks were freed inside the Linux VM.

Unless an explicit TRIM/DISCARD command is passed down through the virtual block device driver to APFS, the host file stays bloated at its peak high-water mark. Your Mac SSD thinks that space is occupied, even if 80% of Docker's internal filesystem is empty air.


4. Diagnosing Docker Bloat from the Terminal

Before touching anything, let’s see what is actually consuming space inside Docker's virtual disk. Run:

docker system df
Enter fullscreen mode Exit fullscreen mode

You will see output similar to this:

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          18        4         12.4GB    8.6GB (69%)
Containers      8         2         840MB     620MB (73%)
Local Volumes   14        3         14.2GB    11.1GB (78%)
Build Cache     142       0         18.9GB    18.9GB (100%)
Enter fullscreen mode Exit fullscreen mode

Notice that Build Cache and Local Volumes often represent the vast majority of bloat!

For a granular breakdown of every individual container, image layer, and volume:

docker system df -v
Enter fullscreen mode Exit fullscreen mode

5. The Step-by-Step Reclamation Guide

Let's methodically reclaim this space, from the safest non-destructive steps to deep pruning.

Step 1: Purge the BuildKit Cache (The Biggest Hidden Hog)

Modern Docker builds use BuildKit by default. BuildKit aggressively caches intermediate compilation layers, npm installs, and apt packages so future builds are fast. But over months of active development, this cache routinely balloons past 20GB.

To reclaim the build cache:

docker builder prune -f
Enter fullscreen mode Exit fullscreen mode

To purge all build cache, including non-dangling build stages:

docker builder prune -a -f
Enter fullscreen mode Exit fullscreen mode

[!TIP]
This does not delete any running containers or active project images. It only clears build cache layers. The next time you build an image from scratch, Docker will simply re-download or re-compile missing layers.

Step 2: Remove Dangling and Unused Images

When you rebuild a Dockerfile with the same tag (e.g. myapp:latest), the previous image build becomes an untagged <none>:<none> dangling image.

To delete dangling images:

docker image prune -f
Enter fullscreen mode Exit fullscreen mode

To delete all images not currently linked to a running container:

docker image prune -a -f
Enter fullscreen mode Exit fullscreen mode

Step 3: Remove Stopped Containers

Every time you docker run without the --rm flag, the container's read-write filesystem overlay remains on disk when it exits:

docker container prune -f
Enter fullscreen mode Exit fullscreen mode

Step 4: Audit Dangling Volumes

Anonymous volumes created by database containers (Postgres, Redis, MongoDB) are persistent by design. If you discard a test container without removing its volume (docker run -v /data), that volume remains forever:

# List orphaned volumes not attached to any container
docker volume ls -qf dangling=true

# Remove dangling volumes safely
docker volume prune -f
Enter fullscreen mode Exit fullscreen mode

[!WARNING]
If you have a local dev database in an anonymous volume that isn't named, pruning volumes will wipe that database! Ensure active databases are backed up or defined with named volumes before running volume prune.


6. Docker Cleanup Strategy Matrix

Here is how the different Docker commands compare in terms of disk savings and safety:

Command What It Cleans Typical Savings Safety Level Risk / Impact
docker builder prune -a BuildKit compilation cache 10 GB – 25 GB 🟢 High Slower initial rebuild for your next docker build
docker image prune Untagged dangling images (<none>) 2 GB – 10 GB 🟢 High Zero impact; unreferenced layers only
docker container prune Stopped containers 1 GB – 5 GB 🟡 Medium Removes logs and ephemeral writable layer of exited containers
docker volume prune Dangling anonymous volumes 5 GB – 30 GB 🟠 Moderate Discards orphaned volume data; protect dev databases first
docker system prune -a --volumes Everything not actively running 25 GB – 70 GB 🔴 Aggressive Wipes all unused images, caches, stopped containers, and volumes

7. Shrinking Docker.raw on APFS

After pruning internally, ensure host APFS actually reclaims the space.

Method A: Docker Desktop Compact

Docker Desktop (v4.20+) includes a native disk compaction utility:

  1. Open Docker Desktop settings.
  2. Go to Settings → Resources → Advanced (or the Troubleshoot bug icon).
  3. Select "Clean / Purge data" or click "Compact virtual disk".

Method B: Resetting the Virtual Disk

If Docker.raw remains stubbornly large, reset it cleanly:

killall Docker
rm -f ~/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw
Enter fullscreen mode Exit fullscreen mode

When restarted, Docker initializes a fresh virtual disk of just ~1.5 GB.


8. Automated Intelligence: Dusty's Docker Overlay Detection

If auditing sparse files and BuildKit layers manually is tedious, Dusty handles it automatically.

Dusty is an AI-powered macOS storage cleaner built in native Swift for developers.

Dusty Interface

How Dusty Handles Docker Storage:

  • Docker VM & Overlay Detection: Automatically pinpoints Docker.raw, displaying true physical usage vs logical size.
  • Google Gemini AI Explanations: Explains obscure cache layers and overlay paths in plain English before cleanup.
  • Trash-First Safety: Files move to macOS Trash rather than permanent deletion.
  • Developer Bloat Scanning: Simultaneously clears orphaned node_modules, Xcode DerivedData, and WhatsApp media.
  • Transparent Pricing: $9.99 lifetime license (pay once, own forever) with a 24-hour free trial. No subscriptions.

Quick Install via Homebrew:

brew install sarmadhussainch/dusty/dusty
Enter fullscreen mode Exit fullscreen mode

GitHub Tap: github.com/sarmadhussainch/homebrew-dusty

Official site: dustystorage.com


Final Thoughts

Docker Desktop is essential for modern development, but on macOS, its storage footprint demands vigilance. Audit docker system df monthly and prune your BuildKit cache to keep your SSD fast and healthy.


What About Your Machine?

How much space was Docker.raw taking on your Mac? Did clearing BuildKit make the biggest difference? Share your numbers in the comments!

Top comments (0)