Deep Clean MacBook
Requirements: macOS + built-in Terminal only, nothing to install โ full walkthrough in SETUP.md
A practical, step-by-step playbook for reviving a MacBook that has slowed to a crawl โ usually because the boot disk is nearly full and macOS has started swapping memory to disk. This is the same diagnostic sequence used to find and fix that: check disk pressure first, find the actual space hogs, clean them in a safe order, then purge swap with a reboot.
This is a guide plus one read-only helper script (cleanup.sh). It does not
delete anything on its own unless you explicitly tell it to.
Why this matters
A MacBook with less than roughly 10-15% free disk space will start compressing and swapping memory aggressively, even if you have plenty of RAM. Symptoms: the beachball on ordinary tasks, fans spinning at idle, Spotlight indexing forever, apps taking seconds to switch. Most people reach for "buy more RAM" or "reinstall macOS" when the real problem is a full disk causing memory pressure. Free the disk, reboot to clear swap, and the machine usually comes back to life without touching hardware or the OS install.
Quickstart
git clone <this-repo-url>
cd deep-clean-macbook
# 1. See what's actually wrong (read-only, no changes made)
sh cleanup.sh --diagnose
# 2. See what WOULD be deleted, without deleting anything
sh cleanup.sh
# 3. Once you've reviewed the list, actually delete it
sh cleanup.sh --execute
# 4. Reboot to purge swap
sudo reboot
Step 1 โ Diagnose: is this actually a disk problem?
Before deleting anything, confirm the theory.
Check free disk space:
df -h /
Look at the Capacity / Avail columns for /. Below roughly 10-15% free
on the boot volume is the danger zone on modern macOS (APFS local
snapshots and system data make this worse than it looks on paper).
Check memory pressure:
memory_pressure
A System-wide memory free percentage well below 20%, or a status of
WARN / CRITICAL, means macOS is already stressed. Memory pressure that
stays high on a machine with 16GB+ RAM is a strong signal the disk is too
full for macOS to manage swap space and local snapshots properly, not that
you need more RAM.
Check swap usage:
vm_stat | grep -E "Pageouts|Swapins|Swapouts"
sysctl vm.swapusage
High Pageouts and a large vm.swapusage figure confirm the machine has
been swapping to disk. Swap only clears on reboot โ quitting apps does not
release it.
Rule of thumb: if df -h / shows less than ~15% free AND
memory_pressure or vm.swapusage looks stressed, the fix below will help.
If disk space is fine and the machine is still slow, the bottleneck is
elsewhere (CPU-bound process, failing disk, thermal throttling) and this
guide will not fix it โ check Activity Monitor's CPU and Energy tabs
instead.
Step 2 โ Find the space hogs
Don't guess. Measure. These are the usual suspects, roughly in order of how often they turn out to be the culprit:
# Overall picture of top-level Library usage
du -sh ~/Library/* 2>/dev/null | sort -rh | head -20
# App sandbox containers โ can silently grow to tens of GB
du -sh ~/Library/Containers/* 2>/dev/null | sort -rh | head -20
# Caches โ almost always safe to clear, macOS rebuilds them
du -sh ~/Library/Caches/* 2>/dev/null | sort -rh | head -20
# Xcode: DerivedData, device support, old simulators โ commonly 20-100GB+
du -sh ~/Library/Developer/Xcode/DerivedData 2>/dev/null
du -sh ~/Library/Developer/Xcode/iOS\ DeviceSupport/* 2>/dev/null
xcrun simctl list devices 2>/dev/null
# CoreSimulator caches
du -sh ~/Library/Developer/CoreSimulator/Caches/* 2>/dev/null | sort -rh
# Docker / colima images and volumes โ these do not shrink on their own
docker system df 2>/dev/null
colima list 2>/dev/null
du -sh ~/.colima/* 2>/dev/null | sort -rh
# node_modules sweep โ find every one, sorted by size, without walking each by hand
find ~ -name node_modules -type d -prune -exec du -sh {} \; 2>/dev/null | sort -rh | head -30
# Trash and Downloads โ the obvious ones people forget to check
du -sh ~/.Trash 2>/dev/null
du -sh ~/Downloads 2>/dev/null
du -sh ~/Downloads/* 2>/dev/null | sort -rh | head -20
# System-level overview (needs Full Disk Access for Terminal to be accurate)
du -sh /private/var/vm 2>/dev/null
du -sh /private/var/folders 2>/dev/null
If you want a visual map instead of scrolling text, Finder โ About This
Mac โ Storage โ Manage gives Apple's own breakdown and is a fine
cross-check.
Step 3 โ Safe deletion order
Delete in this order โ safest and highest-yield first:
- Trash.
rm -rf ~/.Trash/*or just empty it from Finder. Free, no risk, no side effects. - App and browser caches (
~/Library/Caches/*). Apps rebuild these on next launch. Never breaks anything, worst case is a slightly slower next launch. - Xcode DerivedData (
~/Library/Developer/Xcode/DerivedData). Rebuilt automatically the next time you build a project in Xcode. Frequently the single biggest win on a developer machine. - Old iOS DeviceSupport folders and unused simulators. Keep the device
support version matching your current Xcode; delete the rest.
xcrun simctl delete unavailableclears simulators macOS itself has already marked as unavailable. - Docker / colima images you don't currently need.
docker system prune -a(anddocker volume pruneif you know you don't need old volumes) reclaims space colima/Docker never gives back on its own. Stop colima first (colima stop) if you want to also shrink its underlying disk image. - Stale
node_modulesdirectories for projects you're not actively working on. They regenerate withnpm install/pnpm installany time you come back to a project โ never delete one for a project you have open right now. - Downloads folder. The one place most people never clean. Old installers, duplicate zips, exported videos. This is manual triage, not something a script should touch automatically.
- App Containers (
~/Library/Containers/*) โ only for apps you've actually uninstalled. Deleting a container for an app you still use wipes that app's local data/settings.
Do not delete anything under /System, /Library (system-level, not
~/Library), or files you don't recognize under /private/var. Those are
managed by macOS and Apple's own housekeeping (clean_mach, purgeable
space accounting) โ touching them by hand is how people break their
installs.
Step 4 โ Purge swap
Clearing disk space does not immediately release swap that's already in use โ swap only clears on reboot:
sudo reboot
After reboot, re-run the checks from Step 1 to confirm memory_pressure
and vm.swapusage have dropped.
Step 5 โ Prevention
- Keep at least 15% of your boot disk free, always. That's roughly 75GB free on a 500GB disk, or 150GB on a 1TB disk. Below that, macOS starts fighting itself for local snapshot and swap space.
- Re-run
cleanup.sh --diagnosemonthly or whenever things start to feel sluggish, before it becomes a full-blown slowdown. - Prune Docker/colima images periodically โ
docker system dfshows reclaimable space; these never shrink automatically. - Clear old Xcode DerivedData and simulators after major Xcode upgrades โ new versions leave the old ones behind by default.
- Empty Trash and sweep Downloads on a schedule rather than waiting for a crisis โ a recurring calendar reminder works fine.
The cleanup.sh script
cleanup.sh is intentionally conservative:
- Default (no flags), or
--dry-run: prints what it would delete and the size it would reclaim. Deletes nothing. --diagnose: runs the Step 1 checks (disk, memory pressure, swap) and prints them. Deletes nothing.--execute: actually deletes. Requires this flag explicitly โ there is no way to trigger real deletion by accident.- Scope is hard-limited to known-safe, regenerable paths: user-level
caches (
~/Library/Caches), Xcode DerivedData, unavailable simulators, and~/.Trash. It will never touch/System,/Library, arbitrarynode_modules, Docker volumes, or your Downloads folder โ those need human judgment and are listed as commands above for you to run yourself. - Every path it touches is checked against an allowlist of prefixes before
any
rmruns. If a path doesn't match, it's skipped and reported, not deleted.
Read the script before running it with --execute. It is under 150 lines
and worth reading once so you know exactly what it will do on your machine.
Make it your own
- Add or remove paths from the
SAFE_PATHSarray incleanup.shto match your own workflow (for example, add a project-specific build cache directory you regenerate often). - Turn Step 1's diagnostic checks into a LaunchAgent that runs weekly and sends you a notification when free disk space drops below your threshold, instead of waiting until the machine is already crawling.
- If you manage multiple Macs, wrap
cleanup.sh --diagnosein a script you can run over SSH against each one to spot problems before they page you.
Requirements
- macOS (uses
df,vm_stat,memory_pressure,sysctlโ all built in, nothing to install). sh/bash(built in).- Optional: Docker/colima CLI if you want the Docker-specific commands to
return output; Xcode command-line tools for the
xcrun simctlcommands.
License
Use it, fork it, change it. No warranty โ read cleanup.sh before running
it with --execute, same as you should with any script that deletes files.