It's 2 a.m. and your CI pipeline fires an alert: build node has less than 2 GB free — xcodebuild exits immediately. You open Terminal, run df -h, and your 256 GB SSD is full. ~/Library/Developer alone holds 180 GB, and a folder called DerivedData accounts for more than 90 GB of it.
This is not a one-off. On an M4 Mac Mini that has run for eight months without cleanup, DerivedData typically accumulates 40–100 GB; add simulator images and device symbol files, and ~/Library/Developer topping 150 GB is common. This article walks you through root cause → fast diagnosis → per-folder cleanup → CI prevention → automated maintenance — a playbook you can copy directly.
By the end you will have: a safety guide for each DerivedData subdirectory, a one-liner to find the biggest disk hog, cleanup scripts for both local and CI, and an automation setup so disk alerts stop coming back.
Quick Answer: three most common questions
| Question | Direct answer | Risk |
|---|---|---|
| Can I delete DerivedData? | rm -rf ~/Library/Developer/Xcode/DerivedData/* is completely safe |
Next build is full rebuild — about 2–5 minutes slower |
| Can I delete Archives? | Versions with no active users: yes; keep dSYM for live releases | Cannot symbolicate crash logs for deleted versions |
| CI node disk almost full — quick fix? | DerivedData → unavailable simulators → old DeviceSupport, in that order | Next build rebuilds indexes — about 3 minutes slower |
What DerivedData actually stores
Many developers know "clear DerivedData fixes weird build issues" but not what is inside. That leads to two mistakes: deleting something you should keep, or keeping what should go — and watching the disk fill up again. Here are the most important paths under ~/Library/Developer:
| Path | Contents | Typical size | Safe to delete? |
|---|---|---|---|
Xcode/DerivedData |
Build artifacts, indexes, build logs, Swift module cache | 5–60 GB | ✅ Fully safe — rebuilt automatically on next build |
Xcode/Archives |
Release builds (.xcarchive) including dSYM symbol files | 2–30 GB | ⚠️ Old versions OK; keep dSYM for live releases |
Xcode/iOS DeviceSupport |
Debug symbols downloaded when connecting physical devices | 1–3 GB per iOS version | ✅ Old versions OK — re-downloaded on reconnect |
CoreSimulator/Profiles/Runtimes |
Simulator runtime images | 5–10 GB per iOS version | ⚠️ Unused versions OK — must re-download |
CoreSimulator/Devices |
Created simulator instance data | 1–15 GB | ✅ Unavailable state — safe to delete |
Caches/org.swift.swiftpm |
SPM package cache | 1–8 GB | ✅ Safe — rebuilt on next resolve |
Caches/com.apple.dt.Xcode |
Xcode internal cache (previews, IB) | 0.5–3 GB | ✅ Safe |
DerivedData subdirectory layout
Each project gets its own subdirectory under DerivedData, named {ProjectName}-{random-hash}/. Internal structure:
MyApp-abcdefghijklmn/
├── Build/
│ ├── Products/ # Build outputs (.app, .framework, .o files)
│ └── Intermediates.noindex/ # Intermediate files (.o, .d dependency graph)
├── Index.noindex/ # SourceKit index (jump to definition, autocomplete)
├── Logs/ # Build logs (xcodebuild -resultBundlePath also lands here)
└── info.plist # Project path record
Build/Intermediates.noindex is usually the largest — a mid-size project with 200 Swift files can easily exceed 2 GB here; add Index.noindex and a single project's DerivedData folder hitting 5–10 GB is common.
Five steps to find the disk hog fast
Do not start deleting blindly. Spend three minutes diagnosing — it saves time. This flow was tested on a 256 GB M4 Mac Mini and finds the top 5 directories in under two minutes.
Step 1: Check overall disk usage
# View full-disk usage
df -h /
# Quick top 10 directories (entire root volume)
sudo du -sh /* 2>/dev/null | sort -rh | head -10
Step 2: Break down the Developer directory
du -sh ~/Library/Developer/Xcode/* 2>/dev/null | sort -rh
du -sh ~/Library/Developer/CoreSimulator/Profiles/Runtimes/* 2>/dev/null | sort -rh
du -sh ~/Library/Developer/Xcode/iOS\ DeviceSupport/* 2>/dev/null | sort -rh
Sample output (CI node running 8 months):
91G /Users/ci/Library/Developer/Xcode/DerivedData
28G /Users/ci/Library/Developer/CoreSimulator
18G /Users/ci/Library/Developer/Xcode/iOS DeviceSupport
12G /Users/ci/Library/Developer/Xcode/Archives
6G /Users/ci/Library/Developer/Xcode/Caches
Step 3: Find the biggest DerivedData sub-projects
du -sh ~/Library/Developer/Xcode/DerivedData/* 2>/dev/null | sort -rh | head -10
Step 4: Check simulator usage and status
# List all simulators and their status
xcrun simctl list devices
# Show only "unavailable" (left over after OS upgrades)
xcrun simctl list devices | grep -i "unavailable"
Step 5: Confirm DeviceSupport version distribution
ls -lh ~/Library/Developer/Xcode/iOS\ DeviceSupport/
When output looks like this, you have years of accumulated device symbol files:
drwxr-xr-x 3 dev staff 96B iOS 15.4 (19E241)
drwxr-xr-x 3 dev staff 96B iOS 16.1 (20B82)
drwxr-xr-x 3 dev staff 96B iOS 17.2 (21C62)
drwxr-xr-x 3 dev staff 96B iOS 18.3.2 (22D82)
iOS 15 and 16 symbol files have almost no value in 2026 (App Store minimum deployment targets are widely at iOS 17+) — safe to delete.
Per-folder cleanup: safe, cautious, do not touch
Safe to delete (zero risk)
# 1. Clear all project build caches
rm -rf ~/Library/Developer/Xcode/DerivedData/*
# 2. Delete unavailable simulators
xcrun simctl delete unavailable
# 3. Clear SPM package cache
rm -rf ~/Library/Caches/org.swift.swiftpm/*
# 4. Clear Xcode internal cache
rm -rf ~/Library/Caches/com.apple.dt.Xcode/*
# 5. Clear SwiftUI preview cache
rm -rf ~/Library/Developer/Xcode/UserData/Previews/*
Selective deletion (remove old, keep new)
# Delete device symbols from more than 2 versions ago (keep latest two iOS versions)
# First confirm which versions exist
ls ~/Library/Developer/Xcode/iOS\ DeviceSupport/
# Example: keep only iOS 18.x, delete earlier
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/iOS\ 15*
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/iOS\ 16*
rm -rf ~/Library/Developer/Xcode/iOS\ DeviceSupport/iOS\ 17*
Handle with care: Archives
- Confirm the version is off the App Store or has no active users
- If users remain: export dSYM separately (Organizer → right-click Archive → Show in Finder → copy .dSYM)
- Upload dSYM to Firebase Crashlytics / Sentry or your crash analytics platform
- Delete the Archive only after the platform confirms receipt
One-shot emergency commands (disk critical)
Run these in safe order — typically frees 20–60 GB quickly:
#!/bin/bash
# emergency-clean-xcode.sh
# Before running: confirm no xcodebuild processes are active
echo "=== Xcode disk emergency cleanup ==="
echo "[1/4] Cleaning DerivedData..."
du -sh ~/Library/Developer/Xcode/DerivedData 2>/dev/null
rm -rf ~/Library/Developer/Xcode/DerivedData/*
echo "✓ DerivedData cleared"
echo "[2/4] Deleting unavailable simulators..."
xcrun simctl delete unavailable
echo "✓ Unavailable simulators deleted"
echo "[3/4] Cleaning SPM and Xcode caches..."
rm -rf ~/Library/Caches/org.swift.swiftpm/*
rm -rf ~/Library/Caches/com.apple.dt.Xcode/*
echo "✓ Caches cleared"
echo "[4/4] Cleaning preview cache..."
rm -rf ~/Library/Developer/Xcode/UserData/Previews/*
echo "✓ Preview cache cleared"
echo ""
echo "=== Cleanup complete — current disk status ==="
df -h /
CI/CD pipeline anti-blockage strategies
Manual cleanup on a dev machine is manageable; an unattended CI runner needs systematic protection. Below are three approaches for self-hosted macOS runners (GitHub Actions / GitLab CI).
Option A: Fixed DerivedData path + incremental cache
Point DerivedData to a controlled path and reuse across jobs via CI cache:
# .github/workflows/ios-build.yml excerpt
- name: Build
run: |
xcodebuild \
-project MyApp.xcodeproj \
-scheme MyApp \
-configuration Release \
-derivedDataPath /tmp/derived-data \
-resultBundlePath /tmp/result.xcresult \
build
- name: Cache DerivedData
uses: actions/cache@v4
with:
path: /tmp/derived-data
key: derived-data-${{ runner.os }}-${{ hashFiles('**/Package.resolved') }}-${{ hashFiles('**/*.xcodeproj/project.pbxproj') }}
restore-keys: |
derived-data-${{ runner.os }}-
Package.resolved and project.pbxproj. Otherwise a Swift Package upgrade can hit stale cache and fail the build.
Option B: Auto-clean stale cache before/after builds
# Before build: remove DerivedData subdirectories unused for 7+ days
- name: Clean stale DerivedData
run: |
find ~/Library/Developer/Xcode/DerivedData \
-maxdepth 1 -mindepth 1 \
-type d \
-atime +7 \
-exec rm -rf {} +
echo "DerivedData cleanup done — current size:"
du -sh ~/Library/Developer/Xcode/DerivedData 2>/dev/null || echo "Directory empty"
Option C: GitLab CI disk capacity gate
Add a pre-check job in .gitlab-ci.yml to fail fast when disk is low — avoids builds dying halfway and leaving useless artifacts:
# .gitlab-ci.yml
check_disk:
stage: .pre
script:
- |
AVAIL=$(df -k / | awk 'NR==2 {print $4}')
THRESHOLD=$((10 * 1024 * 1024)) # 10 GB in KB
if [ "$AVAIL" -lt "$THRESHOLD" ]; then
echo "ERROR: Less than 10 GB free (current: $((AVAIL/1024/1024)) GB) — clean up first"
exit 1
fi
echo "Disk check passed — free: $((AVAIL/1024/1024)) GB"
tags:
- macos-runner
Three-option comparison
| Option | Best for | Setup cost | Cache reuse |
|---|---|---|---|
| A: Fixed path + incremental cache | Mid/large projects, frequent PRs, build-time sensitive | Medium (cache action config) | High (cross-job reuse) |
| B: Periodic stale subdirectory cleanup | Multi-project shared nodes, moderate disk headroom | Low (one find command) | Medium (same-day reuse) |
| C: Capacity gate fail-fast | Any scenario — last-line defense | Very low (5-line script) | Does not affect reuse |
Recommended: stack all three — A for speed, B to control growth, C as the safety net.
Automated maintenance: scheduled scripts + capacity alerts
Manual cleanup is unreliable. Wire cleanup into a script and schedule it with macOS launchd — turn disk problems from reactive firefighting into proactive defense.
Full maintenance script
#!/bin/bash
# xcode-maintenance.sh
# Recommended: run every Sunday at 3 a.m.
# Place at: ~/scripts/xcode-maintenance.sh
LOGFILE=~/Library/Logs/xcode-maintenance.log
THRESHOLD_GB=20 # Trigger deep clean when free disk falls below this
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOGFILE"; }
log "=== Xcode scheduled maintenance started ==="
# 1. Record disk state before cleanup
BEFORE=$(df -h / | awk 'NR==2 {print $4}')
log "Free disk before cleanup: $BEFORE"
# 2. Remove DerivedData subdirectories not accessed in 14 days
log "Cleaning DerivedData unused for 14+ days..."
find ~/Library/Developer/Xcode/DerivedData \
-maxdepth 1 -mindepth 1 -type d -atime +14 \
-exec rm -rf {} + 2>/dev/null
log "✓ Stale DerivedData entries removed"
# 3. Delete unavailable simulators
log "Cleaning unavailable simulators..."
xcrun simctl delete unavailable 2>/dev/null
log "✓ Unavailable simulators deleted"
# 4. Clean SPM cache (older than 30 days)
find ~/Library/Caches/org.swift.swiftpm -maxdepth 2 \
-type f -atime +30 -delete 2>/dev/null
log "✓ Stale SPM cache removed"
# 5. If free space below threshold, run deep clean
AVAIL_GB=$(df -g / | awk 'NR==2 {print $4}')
if [ "$AVAIL_GB" -lt "$THRESHOLD_GB" ]; then
log "⚠️ Free disk ${AVAIL_GB}GB < ${THRESHOLD_GB}GB — running deep clean"
rm -rf ~/Library/Developer/Xcode/DerivedData/*
rm -rf ~/Library/Caches/com.apple.dt.Xcode/*
rm -rf ~/Library/Developer/Xcode/UserData/Previews/*
log "✓ Deep clean complete"
fi
AFTER=$(df -h / | awk 'NR==2 {print $4}')
log "Free disk after cleanup: $AFTER"
log "=== Maintenance complete ==="
Register as a launchd scheduled job
Schedule for every Sunday at 3 a.m.:
# 1. Make script executable
chmod +x ~/scripts/xcode-maintenance.sh
# 2. Create launchd plist
cat > ~/Library/LaunchAgents/com.myteam.xcode-maintenance.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.myteam.xcode-maintenance</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/YOUR_USER/scripts/xcode-maintenance.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key><integer>0</integer>
<key>Hour</key><integer>3</integer>
<key>Minute</key><integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/tmp/xcode-maintenance-stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/xcode-maintenance-stderr.log</string>
</dict>
</plist>
EOF
# 3. Load
launchctl load ~/Library/LaunchAgents/com.myteam.xcode-maintenance.plist
echo "Scheduled job registered"
How cloud Mac eliminates disk pain at the root
Everything above mitigates within a fixed disk budget. If your team is evaluating CI infrastructure upgrades, one option deserves serious consideration: replace local nodes with dedicated cloud Mac Mini instances.
Disk management works differently in the cloud:
- Add expanded SSD: Macstripe M4 Mac Mini supports extra storage — buying more disk beats manual cleanup
- Periodic node reset: Monthly nodes can be reprovisioned clean at period end — DerivedData starts from zero, no cleanup scripts needed
- Scale on demand: Add nodes during release sprints, scale back in quiet periods — no paying for peak capacity year-round
- SSH direct debugging: When a build goes weird,
ssh runner@your-nodeand troubleshoot on the machine — easier than remote-managing physical hardware
Real case: a mobile team replaced three local Mac Minis with Macstripe cloud nodes and saved roughly 8–12 hours per month on ops (disk cleanup, Xcode updates, runner troubleshooting). At engineer hourly rates, ops savings alone covered most of the rental cost.
Teams already on GitHub Actions can also read Can You Develop iOS on Windows Without Buying a Mac? A Hands-On Test for a full comparison of building iOS apps across platforms.
FAQ
Q: Will deleting DerivedData affect my source code or release history?
No. DerivedData only stores build artifacts, SourceKit indexes, and build logs — completely independent of source code, Git history, Provisioning Profiles, and App Store Connect submissions. Xcode rebuilds everything on the next build; the only cost is a full instead of incremental build — about 2–5 extra minutes for a mid-size project.
Q: Can I delete the Archives folder freely?
Not without checking first. .xcarchive files in Archives contain dSYM symbol files used to symbolicate production crash logs. Before deleting: ① confirm the version is off the App Store with no active users; ② or upload dSYM to Firebase Crashlytics / Sentry first. Keep Archives for the last 3 release versions; older ones can go after dSYM export.
Q: CI rebuilds from scratch every time — can I reuse cache?
Yes. Pass -derivedDataPath /tmp/derived-data to xcodebuild, then use GitHub Actions actions/cache or GitLab CI cache: for incremental sync. Cache keys must include hashes of Package.resolved and project.pbxproj to avoid cross-version pollution. For Swift Package-heavy projects, incremental builds typically save 40–60% of time.
Q: xcodebuild fails because disk is full — how do I recover quickly?
Run in priority order: ① rm -rf ~/Library/Developer/Xcode/DerivedData/* (usually frees 10–60 GB); ② xcrun simctl delete unavailable (frees 5–20 GB); ③ delete iOS DeviceSupport entries more than two versions old (frees 5–15 GB). Step ① alone fixes most cases.
Q: What's the difference between Product > Clean Build Folder and deleting DerivedData from the command line?
Clean Build Folder only clears build artifacts for the currently open project — no effect on other projects' caches, simulator data, or DeviceSupport. rm -rf ~/Library/Developer/Xcode/DerivedData/* clears all projects' caches. Use the command line for CI node maintenance; Clean Build Folder is more precise for day-to-day dev machine use.
Q: DerivedData grows very fast on a CI runner — what should I do?
Common cause: multiple Schemes / branches building concurrently, each creating a new DerivedData subdirectory. Fixes: ① use -derivedDataPath to pin a single path; ② add find ... -atime +7 -exec rm -rf {} + in your build script to remove subdirectories unused for 7 days; ③ evaluate upgrading node disk or Macstripe cloud Mac expanded SSD options.
Summary
DerivedData disk blockage is something almost every iOS developer hits — but it is fully solvable with a systematic approach, not frantic cleanup every time builds act up.
- Understand the root cause: DerivedData is pure cache — safe to delete; Archives hold dSYM — handle carefully
- Diagnose fast:
du -sh ~/Library/Developer/Xcode/* | sort -rh— one command to find the culprit - Clean by category: DerivedData — delete freely; DeviceSupport — remove old, keep new; Archives — export dSYM first
- CI protection: fixed
-derivedDataPath+ incremental cache + capacity gate — three layers - Automate: mount a maintenance script via launchd, weekly cleanup — shift from reactive to proactive
- Fix at the root: evaluate cloud Mac — expanded SSD or periodic node reset ends disk anxiety
If your team faces frequent CI disk alerts and mid-build failures, run the diagnostic commands in this article first — you can usually free 30 GB or more in 10 minutes. Long term, moving CI to dedicated cloud Mac Mini is the more sustainable path — scale on demand without disk maintenance overhead.