Reclaim macOS Developer Storage Safely
A practical guide to finding and safely reclaiming developer-generated storage on macOS without treating every cache as disposable.
Table of Contents9 sections

A developer workstation where caches, SDKs, and build artifacts accumulate.
If macOS says System Data is eating your disk while Applications and Documents look reasonable, the useful question is not “How do I delete System Data?” It is: which developer artifacts are consuming space, which ones can be regenerated, and what will deletion cost me on the next build?
For the trade-off between a local workstation and always-on automation, see the VPS versus Mac guide for AI coding agents.
Developer machines accumulate Gradle caches, Android emulator images, Xcode DerivedData, simulator runtimes, package stores, container layers, IDE indexes, and local model files. Some are cheap to rebuild. Others represent downloads, local state, or environments that may take real time and bandwidth to restore.
The safest cleanup strategy is therefore simple: measure first, classify second, delete last, then verify the environment you changed.
Why Developer Macs Accumulate So Much Hidden Storage
macOS groups many files that do not fit its headline storage categories under System Data. For developers, a meaningful share can live in hidden directories such as $HOME/Library, $HOME/.gradle, $HOME/.android, package-manager stores, simulator data, and container storage.
The growth is not necessarily a bug. Development tools optimize for iteration speed: they keep dependencies, indexes, compiled intermediates, SDKs, images, and previous build outputs so the next task can start faster.
That creates the central trade-off of developer cleanup: disk space versus regeneration cost.
A 20 GB directory is not automatically a good deletion target. A smaller stale cache that can be recreated in minutes may be a much better target than a large local runtime or model that takes hours to download again.
When several toolchains compete for the same local drive, hidden files expand quietly over months of feature development, branch switching, and local testing. The dangerous part is not simply that the files are hidden. It is that very different kinds of data are often lumped together as “cache” even when some contain emulator state, local databases, downloaded runtimes, or other work that is expensive to recreate. Understanding this distribution prevents costly mistakes when attempting to reclaim gigabytes of disk space.
Reinforces the transition from raw du outputs to structured directory classification.
Start With an Audit, Not rm -rf
Before touching anything, identify where the space actually went across your local drive.
A quick first pass over your user Library helps locate massive subfolders:
du -sh $HOME/Library/* 2>/dev/null | sort -rh | head -n 15
Then inspect common developer-owned locations that standard UI panels rarely detail:
du -sh $HOME/.gradle $HOME/.android $HOME/.npm $HOME/.pnpm-store $HOME/.cocoapods 2>/dev/null | sort -rh
The point is not to immediately delete the largest result. Use the output to build a shortlist, then classify each item according to its true operational nature:
| Class | Examples | Default action |
|---|---|---|
| Regenerable | build caches, indexes, DerivedData | Usually a cleanup candidate |
| Re-downloadable | dependencies, SDK/runtime packages | Multi Agent Review Pipeline download/time cost first |
| Stateful | emulator data, containers, local databases | Inspect before deleting |
| Personal or credential-bearing | project files, keys, config, local secrets | Keep unless you explicitly know the impact |
Current developer-cleanup tools increasingly use the same basic safety pattern: scan selected locations, show exact paths and sizes, and require a review before removal. That is a good pattern, but the important lesson is independent of any cleaner app: you should understand what owns a directory and how it comes back before deleting it.
Android: Gradle and Emulator Storage
For Android work, two areas commonly deserve attention: Gradle’s local caches and Android Virtual Devices.
Gradle caches
Before removing Gradle cache data, stop active daemons to avoid file locks or corrupted state:
./gradlew --stop
If you decide to clear $HOME/.gradle/caches, expect the next build to download dependencies and rebuild transformed artifacts. That makes the directory regenerable, but not free to regenerate, especially on a slow connection or a project with a large, complex dependency graph.
Treat $HOME/.gradle/wrapper/dists similarly. Old distributions may be disposable, but deleting every distribution means future builds can download them again from scratch.
Android Virtual Devices
Check how much each AVD consumes in your local configuration directory:
du -sh $HOME/.android/avd/*.avd 2>/dev/null
For unused devices, Android Studio’s Device Manager is preferable to blindly deleting directories because it keeps the cleanup aligned with the tool that owns the emulator configuration and associated preference files.
For active devices, remember that an AVD can contain snapshots, user credentials, and local test state. “Can be recreated” is not the same as “has no cost to recreate” once you factor in custom application state.
Xcode: DerivedData Is Different From Simulator State
Xcode’s DerivedData is a classic cleanup candidate because it contains generated build products, symbol files, and IDE indexes:
du -sh $HOME/Library/Developer/Xcode/DerivedData 2>/dev/null
Removing stale DerivedData can reclaim substantial space, with the predictable cost of slower indexing and compilation on the next initial run.
Simulator cleanup deserves more care. Instead of treating the entire simulator directory as cache, let simctl remove simulator entries that Xcode already considers unavailable or orphaned:
xcrun simctl delete unavailable
That distinction matters: generated build output and developer state are not the same category just because both live under a toolchain directory.
Package Managers: Prune Before You Purge
Package managers often provide their own native cleanup commands. Prefer those when available because the tool understands its internal storage model better than a generic filesystem deletion.
For example, managing package stores safely usually involves dedicated verification commands:
pnpm store prune
npm cache verify
A blanket npm cache clean --force is rarely a useful first move. Start with inspection and the package manager’s supported maintenance path, then escalate only when you have a specific diagnostic reason.
The same principle applies to Homebrew, CocoaPods, Docker, local AI models, and other developer tooling: use the owning tool’s prune or cleanup command when it exists, and inspect stateful data separately.
Visualizes the dual dimensions of statefulness and regeneration cost.
Why “Safe to Delete” Is Not Enough
Many cleanup guides correctly separate obvious build artifacts from riskier data. The missing question is often what regeneration costs you in terms of time, bandwidth, and interruption.
Two folders can both be technically rebuildable while having very different practical consequences. DerivedData may cost a slower next compile. A package store may cost gigabytes of downloads over a capped connection. A simulator runtime may take significant time to reinstall. A local AI model may be replaceable but expensive to fetch again. A container volume may not be disposable at all if it contains development data you never exported.
So use two fundamental dimensions before deleting anything:
- Statefulness: does this directory contain unique local state, or only derived artifacts?
- Regeneration cost: if it is derived, how much time, bandwidth, and setup does rebuilding it require?
That small distinction turns cleanup from “delete the biggest cache” into a decision you can explain and repeat safely across multiple development machines.
The Cleanup Order I Would Use
A conservative developer cleanup can follow this structured order to minimize risk:
- Measure the largest developer-owned directories to establish a clear inventory.
- Remove stale generated outputs such as old build artifacts and IDE indexes.
- Prune package-manager caches using supported native commands.
- Remove unused runtimes and emulators through their owning tools.
- Review containers, local databases, and AI models separately because they can contain expensive-to-recreate or stateful data.
- Re-measure disk usage before doing anything more aggressive.
This ordering deliberately leaves broad “system cleanup” until last. The goal is not to make every cache zero bytes. It is to reclaim enough space without turning tomorrow’s development session into an unexpected environment-recovery exercise.
There is a related infrastructure question here too: if local storage pressure is partly caused by long-running development agents, containers, or automation, moving the right workloads off the laptop can be more durable than repeatedly cleaning them. I explored that trade-off in Should Your AI Coding Agent Run on a VPS or Your Mac?.
Verify the Environment After Cleanup
Cleanup is not finished when Finder reports more free space. It is finished when the development environment still behaves predictably under normal workloads.
For an Android project, run the normal project verification path rather than inventing a special cleanup-only test harness. That may include compiling the core module:
./gradlew build
Then verify the tools you actually touched: start an emulator if you removed AVDs, open Xcode if you cleared DerivedData, run the relevant package install if you pruned a store, and confirm local containers or databases still exist if you intentionally preserved them.
The first build may be slightly slower. That is expected when you deliberately trade cached work for local disk breathing room.
A Better Mental Model for System Data
For a developer, System Data is less useful as a category than as a symptom of how modern toolchains operate. The actionable information lives one level lower: path, owner, purpose, regeneration cost, and statefulness.
That leads to a much safer rule than blindly deleting caches:
Delete what you can explain, after you know how it comes back.
If you cannot explain a large directory yet, keep auditing. Reclaiming 20 GB confidently is far better than reclaiming 100 GB and spending the next afternoon rebuilding a broken workstation configuration from scratch.
Continue Exploring
You Might Also Like

Speed Up Node.js CI: Cache Dependencies Without Shipping node_modules
Learn how to speed up Node.js CI and template deployments with reproducible installs, lockfile-aware dependency caching, and zero-build runtime patterns.

Clean Build Artifacts in CI Without Breaking Your Pipeline
Learn how to clean generated build artifacts safely in CI by separating disposable workspace output from caches and release artifacts, then verifying the pipeline from a clean checkout.

Software Project Submission Checklist: Make Your Work Easy to Verify
A practical software project submission checklist for making a repository reproducible, reviewable, and easy to evaluate from a clean checkout.