๐Ÿ— KeyzHub
19Keys ยท community archive

deep-clean-macbook

Step-by-step playbook to revive a slow MacBook: diagnose swap thrash, find space hogs, clean safely.

โ—† 2 commits history main
CLONE THIS PROJECT
git clone https://hub.19keys.com/deep-clean-macbook.git

Public read access โ€” no account needed. Build your own from it.

main
ยท .gitignore
ยท cleanup.sh
ยท README.md
ยท SETUP.md
README

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:

  1. Trash. rm -rf ~/.Trash/* or just empty it from Finder. Free, no risk, no side effects.
  2. App and browser caches (~/Library/Caches/*). Apps rebuild these on next launch. Never breaks anything, worst case is a slightly slower next launch.
  3. 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.
  4. Old iOS DeviceSupport folders and unused simulators. Keep the device support version matching your current Xcode; delete the rest. xcrun simctl delete unavailable clears simulators macOS itself has already marked as unavailable.
  5. Docker / colima images you don't currently need. docker system prune -a (and docker volume prune if 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.
  6. Stale node_modules directories for projects you're not actively working on. They regenerate with npm install / pnpm install any time you come back to a project โ€” never delete one for a project you have open right now.
  7. 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.
  8. 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 --diagnose monthly or whenever things start to feel sluggish, before it becomes a full-blown slowdown.
  • Prune Docker/colima images periodically โ€” docker system df shows 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, arbitrary node_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 rm runs. 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_PATHS array in cleanup.sh to 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 --diagnose in 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 simctl commands.

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.