← Writing

How to free up disk space on a Mac as a developer

Where developer disk space goes—Xcode DerivedData, Gradle, node_modules, Docker—and how to clean it safely by hand or with reclaim, an open-source CLI.

On this page

Every few months, my Mac runs out of space — and the culprit is rarely photos or videos. It’s 19 GB of Gradle caches, 8 GB of Xcode DerivedData, and node_modules directories from projects I haven’t opened in a year.

I work across Flutter, Node, Python, iOS, and Docker, so every stack leaves behind its own build artifacts and package caches. macOS reports them as storage usage, but it can’t tell me which ones are safe to remove.

The last time this happened, I documented the cleanup and turned what I learned into reclaim, an open-source CLI that finds developer-generated disk usage and helps remove it safely. My last full cleanup with it freed 98 GB. This post covers the manual route first — what to inspect, what to delete, what to leave alone — and then the tool.

First, see where the space is going

Before deleting anything, measure. These commands are read-only:

du -sh \
  ~/Library/Developer/Xcode/DerivedData \
  ~/.gradle/caches \
  ~/.npm \
  2>/dev/null | sort -h

docker system df -v
brew cleanup --dry-run

Most of what you’ll find is regenerable — but “regenerable” doesn’t always mean “zero consequence.” The way to stay safe is to sort everything into three buckets: rebuilds itself, review first, and never touch.

Safe to rebuild: the big caches

Xcode DerivedData — intermediate build products and indexes. Often the single biggest cache on an iOS/macOS developer’s machine. Deleting it means the next build and indexing pass take longer; nothing is lost:

rm -rf ~/Library/Developer/Xcode/DerivedData

Gradle — Gradle does clean unused cache entries periodically, but on a machine that builds several Android or JVM projects the cache still grows into the tens of gigabytes. Mine was 19 GB. The next build re-downloads what it needs:

rm -rf ~/.gradle/caches

JavaScript package caches — these are the global download caches, separate from any project’s node_modules. npm’s cache is self-healing: npm cache verify checks integrity and garbage-collects without dropping everything, while clean --force is the reclaim-disk-space option:

npm cache verify          # integrity check + garbage collection
npm cache clean --force   # or: empty the cache entirely
pnpm store prune
yarn cache clean

Homebrewbrew cleanup removes old versions and stale downloads. Homebrew runs this automatically every 30 days, so the gains are usually modest — the --dry-run from earlier shows exactly what it would remove.

Review first: Docker and stale projects

Docker deserves inspection before deletion. Start narrow:

docker system df -v    # what's actually using space
docker builder prune   # build cache only

docker system prune is the broader sweep: it also removes all stopped containers and unused networks. A stopped container can hold data in its writable layer that does not regenerate — check docker ps -a before running it. One macOS-specific nuance: Docker Desktop stores all Linux-side data in a single large disk image, so unused images and build cache consume real host space, and deleting files inside a running container doesn’t necessarily return that space to macOS.

Stale node_modules — the sneaky one. Each folder is only a few hundred megabytes, but across every project you’ve ever cloned it often outweighs any single cache. It’s reproducible as long as the lockfile is valid and the packages are still available from the registry — true for almost anything recent, less certain for a six-year-old project. The judgment call is which projects: in an active repo you’re just paying for a reinstall, so target the ones you’ve genuinely abandoned. The same logic applies to Rust target/ directories and Python virtualenvs.

What should never be on the list

Source code. Anything under version control — especially with uncommitted or unpushed work. Dotfiles, credentials, and profile data. Databases belonging to local services. No cleanup pass should go near these, no matter how large they’ve grown; if a “cache” folder might contain any of them, that’s a review-first item at best.

Where manual cleanup stops scaling

The manual route works. The problem is that it’s a scavenger hunt across a dozen tools, the safety judgment is different for each one, and the list keeps growing — Cargo, CocoaPods, Playwright’s browser binaries, Hugging Face model caches, simulator runtimes. I did the hunt one time too many.

What reclaim does differently

reclaim is a single-binary CLI (Go, MIT-licensed) that scans everything at once and presents an interactive picker:

🧹 reclaim — interactive cleanup
Selected: 21.4 GB of 47.2 GB

▼ Build caches — 25 GB [3/3 items, 25 GB selected]
    [✓] ✓ ~/.gradle/caches                                  19.2 GB
    [✓] ✓ ~/.gradle/wrapper                                   4.2 GB

▼ Package manager caches — 7.7 GB [4/8 items, 4 GB selected]
    [✓] ✓ ~/.npm/_cacache                                    3.3 GB
    [ ] ✓ ~/.pub-cache                                       3.1 GB

▼ IDE & editor caches — 8.3 GB [1/3 items, 8.0 GB selected]
    [✓] ✓ ~/Library/Developer/Xcode/DerivedData              8.0 GB

The catalog covers 70+ rules across build caches (Gradle, Maven, Go, Cargo, Bazel), package managers (npm, pnpm, Bun, pip, uv, poetry, conda, CocoaPods, Homebrew), IDE artifacts (Xcode, VS Code, Cursor, JetBrains), containers (Docker, Colima), testing tools (Playwright, Cypress), and AI/ML caches (Hugging Face, Ollama, PyTorch).

Two things do the work the manual route can’t:

  • Git-based staleness detection. For project artifacts like node_modules and target/, reclaim reads each project’s git history to see when you last touched it. Abandoned projects get flagged; the repo you deployed yesterday doesn’t.
  • Tool and app awareness. Vendor cleanups like docker system prune are only offered when the tool is actually available to run them, and rules can require apps like Docker Desktop or Xcode to be closed before their managed files are touched.

reclaim isn’t the only cleaner that recognizes developer caches — commercial tools have started covering npm, Gradle, and Docker too. Its difference is transparency and project awareness: it’s free and open source, uses git history to identify stale artifacts, assigns every rule a visible safety tier, records every deletion, and lets you add rules without touching the Go code. No subscription, no telemetry, one auditable binary.

The safety model

A cleanup tool’s failure mode is catastrophic, so safety is the design center:

  • Every rule carries a visible tier.safe regenerates with zero loss; ⚠ confirm should be reviewed first; ✗ dangerous is never auto-deleted — the picker won’t even let you select it.
  • Deletion is always an explicit choice. Interactive cleanup asks before deleting, and “Quick clean” touches safe items only. In automation, passing --apply is the explicit opt-in (it covers safe and confirm tiers); without it, --plain only reports what it finds.
  • A last line of defense in code. For filesystem deletions, an independent path guard rejects system paths, anything outside your home directory, and the home directory itself — regardless of what the rule catalog says. The handful of rules that delegate to vendor commands (brew cleanup, docker system prune) do what the vendor tool does, which is exactly why they sit behind the review tiers above.
  • An audit trail. Every apply run appends a JSONL record to ~/.reclaim/logs/ — rule, path, bytes freed, errors — so you can always answer “what did it delete last Tuesday?”
  • No telemetry or usage reporting. Cleanup runs entirely locally. Interactive modes can make one optional daily request to GitHub to check for updates; disable it with --no-update-check or RECLAIM_NO_UPDATE_CHECK=1. --plain and --apply never check.

Install reclaim and run a dry scan

Homebrew on macOS (via the ImadRashid/tap):

brew install ImadRashid/tap/reclaim
reclaim --plain

--plain deletes nothing — it prints a report of how much developer-generated storage your Mac has accumulated, which is the right first run. Prebuilt binaries for macOS (Apple Silicon and Intel) and Linux are on the releases page; it’s a single static file with no runtime.

FAQ

Does it work on Linux or Windows? Linux binaries ship with every release and most rules apply there. Windows isn’t supported.

Can I add my own rules? Yes — a rule is a YAML entry (paths, safety tier, one-line description), no Go code involved.

What’s planned next? Planned improvements are tracked in the GitHub repository.


If your Mac is perpetually 10 GB from full, a dry scan will probably surprise you. And if reclaim helps, a star on GitHub makes it easier for other developers to discover. It’s the second small tool I’ve open-sourced recently — the first, csm, manages multiple Claude Code accounts the way nvm manages Node versions.

← All writing Book a call →
Book a call → WhatsApp