React Native and Expo let most day-to-day work happen on Windows or Linux — Metro, Jest, and Android Gradle all run fine there. The moment you need a signed iOS binary, a simulator matrix, or eas build --platform ios with native modules, you still need macOS + Xcode. In 2026 the pragmatic pattern for distributed teams is a leased remote Mac in US East or Asia-Pacific, sized across three M4 tiers, with warm CocoaPods and SwiftPM caches and a honest comparison against EAS Build minutes. This guide walks that decision matrix, lands a GitHub Actions self-hosted runner on the Mac, and closes with an SSH/VNC acceptance checklist plus FAQ.
1. Windows/Linux dev, macOS build: split the workflow cleanly
Keep npx expo start, lint, and unit tests on developer laptops. Push only the iOS lane to the remote Mac: pod install, xcodebuild or eas build --local, and signing. Pick US East-adjacent nodes when most engineers sit in the Americas; pick Singapore, Tokyo, Seoul, or Hong Kong when APAC dominates — measure git fetch and SSH/VNC RTT from each office, not a vendor latency map. Expo's cloud builders solve convenience but bill in compute minutes that spike when you add iOS simulators, cache misses, or monorepo prebuilds; a dedicated Mac trades minutes for predictable wall-clock rent you control. For hybrid routing between Apple-managed queues and rented overflow, see the Xcode Cloud minutes vs rented Mac selection FAQ — the same framing applies to EAS quota planning.
2. Three M4 tiers for React Native / Expo iOS CI
- Tier A — Mac mini M4 (16 GB / 256 GB): one app, Expo prebuild occasionally, <5 iOS builds per week. Run
expo run:iosor a singleeas build --locallane; prune~/Library/Developer/Xcode/DerivedDataafter every job. - Tier B — M4 + 1TB or 2TB SSD: brownfield apps with heavy CocoaPods, multiple schemes, or retained
.xcarchiveartifacts. Holds 2×–4× disk peak when Pods, Hermes, and React Native codegen land in the same pipeline. - Tier C — M4 Pro: daily iOS builds, parallel simulators, and large
expo prebuildtrees where unified memory beats adding another baseline mini.
React Native teams usually hit disk before CPU. Node modules on the laptop are irrelevant — the Mac still ingests Pods, SwiftPM checkouts, and Xcode intermediates on every clean pipeline.
3. CocoaPods and SwiftPM cache layout on the remote Mac
Pin caches to paths your runner user owns and never share them across concurrent jobs without locks:
- CocoaPods: set
CP_HOME_DIRor a repo-localvendor/bundlemirror; keep~/Library/Caches/CocoaPodson the expanded SSD tier sopod installsurvives reboots. - SwiftPM: preserve
~/Library/Caches/org.swift.swiftpmand~/Library/Developer/Xcode/DerivedDatawith per-branch subfolders orgit worktreeisolation. - Expo: cache
~/.expoand your prebuild output directory only when the native project hash is stable — bust keys onapp.jsonplugin changes.
For blobless Git, shallow clones, and queue discipline on large monorepos, follow the large-repo cold start, CocoaPods, and SwiftPM disk FAQ before you scale runner count.
4. Decision matrix: 1TB/2TB fleet vs single M4 Pro vs EAS minutes
| Dimension | EAS Build minutes | 1TB remote Mac | 2TB / dual fleet | Single M4 Pro |
|---|---|---|---|---|
| Cost curve | Per-minute; spikes at peak | Daily/weekly rent, predictable | PR + nightly split across nodes | Single queue, low ops overhead |
| Cache control | Platform-managed | Pods/SwiftPM persistent on disk | High — distribute peak load | High — all caches on one host |
| Storage fit | N/A (cloud-managed) | 1 app + weekly archive prune | Multi-app + dSYMs retained | Large DerivedData, monorepo |
| Best for | Infrequent iOS releases, thin native layer | Daily builds below EAS quota line | Monorepo, multi-flavor white-label | Single brownfield app, heavy Swift |
| Lease flexibility | Cancel anytime (quota plan) | Return after release sprint | Scale second node in/out | Downshift to Tier B if <50% used |
A common hybrid: self-hosted runner on a remote Mac for PR lanes, EAS for Release builds. On release sprint weeks, rent a second Tier B node by the day and return it after the TestFlight upload. Visit the Macstripe pricing page to compare tiers and SSD upgrade costs.
5. GitHub Actions self-hosted runner on the leased Mac
Register the Mac with labels that describe reality: macos-14, xcode-16, region-us-east, expo-ios. Install the runner under a dedicated service account, use concurrency groups so one repo cannot occupy every core during release week, and pass artifacts to Linux jobs instead of re-cloning on Windows. Store signing secrets in environment-scoped GitHub secrets or OIDC — never commit EXPO_TOKEN beside App Store Connect keys in the same plaintext file. For fleet hygiene across multiple repos, read the self-hosted Mac runners, Actions cache, and persistent disk FAQ.
npm ci → npx expo prebuild --platform ios (if needed) → xcodebuild or eas build --local → upload .ipa artifact.6. SSH/VNC acceptance checklist (sign-off before production)
- SSH: key-only auth,
ssh -o BatchMode=yessucceeds from CI bastion;xcodebuild -versionandnode -vmatch workflow pins. - Keychain: CI keychain unlocks non-interactively; distribution cert visible to the runner user.
- VNC: reachable only via VPN/bastion; login session can open Xcode Accounts once; then return to headless SSH for daily builds.
- Runner:
./run.shsurvives reboot (launchdplist); labels visible in GitHub UI; dry-run workflow completes green. - Disk: >25% free after a full iOS archive + Pods install; cache directories owned by runner user.
7. React Native / Expo iOS FAQ
pod installfails on CI but works locally: Ruby version drift — pin withbundle execand commitGemfile.lock. Verify the runner uses the same Ruby major as your local Homebrew.- Hermes / codegen mismatch: clean DerivedData and reinstall Pods after upgrading React Native; align
expoSDK with the Xcode image version on the Mac. - EAS local build cannot find credentials: export
EXPO_TOKENin the runner environment or place an ASC API key in the Mac keychain profile; never commit secrets beside the runner config. - Simulator boot loop:
xcrun simctl erase alland accept the Xcode license withsudo xcodebuild -license acceptbefore running headless jobs. - Metro works on Windows but iOS archive fails: expected — only the Mac lane needs Xcode; do not waste EAS minutes re-running Android steps on the Mac node.
- Should I rent one Mac or two for a release sprint? If your longest iOS build exceeds 20 minutes and you also need a signing/upload lane running in parallel, two Tier B Macs beat one M4 Pro on short leases — return the second node after the sprint.
- US East vs APAC: which region should I pick? Measure
git fetchlatency and SSH round-trip from your primary dev location, not a vendor map. If the majority of engineers are in East/Southeast Asia, a Singapore or Tokyo node typically beats a US East node by 80–150 ms RTT. - Is 1TB enough or do I need 2TB? 1TB fits one active app with weekly
xcarchivepruning. Choose 2TB if you retain dSYMs for crash symbolication, run multiple white-label targets, or let DerivedData accumulate across branches without a nightly cron. - Can I switch regions mid-lease? Check the current availability on the Macstripe pricing page — region transfers depend on inventory. Many teams start with a week-to-week lease to trial latency before committing to a monthly term.
- GitHub Actions runner labels conflict across two repos — how do I isolate? Add a repo-specific suffix to the label (e.g.
macos-arm64-appA) and setconcurrencygroups per workflow so a release job in one repo cannot starve PR checks in another.
Why Mac mini M4 is the right anchor for Expo iOS overflow
Expo abstracts much of the stack, but Apple still requires native macOS for signing and simulator fidelity. Mac mini M4 delivers strong xcodebuild throughput, ~4 W idle power for always-on runners, and a small footprint that fits a pure-SSH CI room. macOS gives you Homebrew, native Node, and Xcode without WSL hacks; Gatekeeper, SIP, and FileVault matter when the host stores distribution certificates. Apple Silicon unified memory keeps React Native's codegen and linker phases steadier than same-price x86 boxes. If you are ready to park iOS builds on dedicated metal beside your Windows/Linux fleet, start with a Mac mini M4 in the region your engineers actually use — visit the Macstripe home page to compare tiers and SSD options, then apply this matrix to beat EAS minute surprises on your next sprint.