Kurz gesagt: Bei rund 500 iOS-CI-Builds pro Tag brauchen die meisten Teams keine acht Mac minis — drei Apple-Silicon-Cloud-Macs reichen, wenn Sie Jobs in GitHub Actions sauber schichten und Self-hosted Mac Runner in eine Fast- und eine Release-Warteschlange trennen, statt auf jeder Maschine parallel drei vollständige Archives laufen zu lassen.
Vor drei Monaten haben wir die Xcode-CI-Umgebung eines iOS-Teams übernommen. Ausgangslage:
- 18 Entwicklerinnen und Entwickler
- 2 Apps in einem gemeinsamen Monorepo
- GitHub Actions als einzige CI-Plattform
- Etwa 500 macOS-Jobs pro Tag (PR-Checks, Unit-Tests, Nacht-Releases)
Die Beschaffungsvorlage sah vor: acht Mac minis im Serverraum als Self-hosted Runner. Wir haben zwei Wochen Workflow-Logs nach Job-Typ aufgeschlüsselt — und die Realität wich stark von der Bauchgefühl-Rechnung ab: Etwa 70 % PR-Validierung und Integrations-Builds, 20 % Simulator-Unit-Tests, nur rund 10 % Archive plus Upload (TestFlight-Upload lässt sich separat buchen, siehe Abbildung 1).
Abb. 1 · Tagesmix bei ~500 iOS-CI-Builds (zwei Wochen GitHub Actions, Messwerte des Teams)
Kapazität rechnen: von 91 auf 63 Maschinenstunden
500 Jobs pro Tag über 24 Stunden sind im Mittel etwa 21 Jobs pro Stunde — entscheidend sind aber Spitzen: Vormittags-Merges in DACH und US-East, PR-Stürme vor Release-Tagen. Peaks liegen oft beim Drei- bis Fünffachen des Mittelwerts. Kapazität muss für Warteschlangen in Spitzenzeiten dimensioniert werden, nicht für den Mittelwert um Mitternacht.
Der zweite Hebel ist die Job-Dauer: Simulator-Tests dauern typisch 4–8 Minuten; PR-Builds mit Dependency-Auflösung 12–20 Minuten; Release-Archive inklusive Notarisierung und Upload oft 25–45 Minuten. Aus zwei Wochen P50 des Teams: leichte Jobs ~6 Minuten, schwere ~35 Minuten. Wer fälschlich 30 % Archive annimmt, kommt auf „acht Macs nötig“ — nach Aufschlüsselung sind schwere Jobs bei 500 Runs deutlich seltener als leichte.
Ohne Cache ergibt eine Grobrechnung (325 leicht + 175 schwer) etwa 91 Maschinenstunden pro Tag — mehr als drei Macs physikalisch liefern (72 Stunden). Mit DerivedData-/SPM-Cache, L2-Serialisierung und Queue-Labels sinken leichte Jobs auf ~4 Minuten, schwere im Warm-Pfad auf ~22 Minuten; der Bedarf fällt auf etwa 63 Maschinenstunden. Bei einem Peak-Faktor ~1,3 ist das tragbar — die Mathematik hinter „drei reichen“.
Abb. 3 · Täglicher macOS-Maschinenstunden-Bedarf (Modell: 500 Jobs/Tag)
Xcode-CI schichten: Archive nicht in dieselbe Runner-Warteschlange wie PRs
Pipeline-Setups, die 500 Runs pro Tag tragen, nutzen fast immer drei Xcode-CI-Stufen:
- L0: SwiftLint, Einzelmodul-Compile — Label
macos-fast. - L1: PR-Integrationsbuild ohne Archive — ebenfalls Fast Mac Runner, pro Knoten bis zu 2 leichte Jobs parallel.
- L2: Archive, Notarisierung, TestFlight — Label
macos-archive, maximal 1 Job gleichzeitig pro Maschine.
GitLab CI, Buildkite und Jenkins folgen demselben Prinzip: Routing per Label, keine Sammelwarteschlange. Burst-Elastizität mit Buildkite: Buildkite Mac Agent an Cloud Mac. Einordnung Mac VPS vs. dedizierter Runner-Pool: Mac-VPS-Leitfaden.
Drei Cloud Macs: Rollen und Topologie
Die produktive statische Drei-Knoten-Topologie (Namen flexibel, Rollen bitte beibehalten):
| Knoten | Runner-Label | Aufgabe | Parallelität |
|---|---|---|---|
| Mac-A | macos-fast |
PR Build, Unit Test | 2 × L0/L1 |
| Mac-B | macos-fast |
Symmetrisch zu Mac-A, Fast-Queue | 2 × L0/L1 |
| Mac-C | macos-archive |
Archive, Notarisierung, Upload | 1 × L2 |
Abb. 2 · Drei-Knoten-Topologie (GitHub Actions Self-hosted Runner)
Zwei Fast-Knoten liefern Durchsatz, ein Release-Knoten liefert planbare Auslieferung. Steht die L2-Warteschlange am Release-Tag über dem SLO, kann Mac-A temporär macos-archive tragen — dann L0-Parallelität abschalten, sonst konkurrieren Keychain und DerivedData-Locks und Signierung bricht sporadisch. Alle drei Xcode-Minor-Versionen müssen mit den Xcode Release Notes übereinstimmen.
Warum GitHub Actions Mac Runner schnell in die Warteschlange rutschen
Viele Teams starten mit GitHub-gehosteten macOS Runnern und stellen bei PR-Flut Wartezeiten fest. Selten liegt es an „GitHub ist langsam“, sondern an: (1) org-weitem macOS-Concurrency-Limit; (2) Workflows, die standardmäßig volle Archives pro PR fahren; (3) fehlender Trennung nach Job-Typ bei Self-hosted Mac Runner — schnelle Jobs hängen hinter langsamen. Vor dem Umzug auf drei dedizierte Cloud Macs lag queue_wait P95 bei gehosteten Runnern über 40 Minuten; mit Fast-/Archive-Doppelqueue sank L1-P95 unter 8 Minuten.
Praxis: drei Cloud Macs als Self-hosted-Baseline, gehostete Runner nur für Open-Source-Wochen oder extreme Spitzen auf L0 — Kosten kontrollierbar, Secrets müssen nicht wöchentlich migriert werden.
Self-hosted Runner vs. Xcode Cloud
Xcode Cloud passt zu Teams, die tief in Apples Ökosystem bleiben und null Agent-Betrieb wollen. Self-hosted Mac Runner passen, wenn Sie Warteschlangen, Cache-Keys und die Mischung mit internem Jenkins/Buildkite steuern müssen. Vergleich:
| Dimension | Xcode Cloud | Cloud Mac + Self-hosted Runner |
|---|---|---|
| Abrechnung | Minutenpaket + Concurrency-Cap | Cloud-Mac-Abo/Tag, planbare Maschinenstunden |
| Warteschlange | Plattformweit | Eigene fast/archive-Labels |
| Secrets | ASC-Integration bequem | Match / Keychain — eigenes Runbook |
| Ideal für | Seltene Releases, wenig Customizing | 500+ Runs/Tag iOS CI/CD |
Wann nach Minutenpaket-Engpass auf Tages-Cloud-Mac wechseln, steht in der FAQ Xcode Cloud Minutenpaket vs. Cloud Mac für Archive.
Warum dieses Team Xcode Cloud abgelehnt hat
Xcode Cloud wurde evaluiert; gewählt blieb GitHub Actions + Self-hosted Runner, weil: (1) Backend und Android bereits auf GitHub laufen — kein zweites CI; (2) eigene Cache-Keys und Monorepo-Matrix nötig; (3) in Release-Wochen Job-Volumen über dem Komfortbereich des Minutenpakets, Warteschlange nicht steuerbar. Xcode Cloud ist nicht schlecht — es passt schlecht zu „500 Runs/Tag, Queue unter Kontrolle“.
Bitrise vs. Self-hosted Mac Runner — Kostenintuition
Bitrise und ähnliche Mobile-DevOps-SaaS sparen Agent-Ops, berechnen aber Concurrency-Tarife. Für 18 Personen, zwei Apps, ~500 Jobs/Tag liegen Jahreskosten oft über einem Abo für drei Cloud Macs, und Archive-Parallelität bleibt am Tarif gebunden. Bitrise eignet sich für Startups ohne Runner-Lust; wer zwei Wochen in Self-hosted Mac Runner investiert, amortisiert Knoten oft in 3–6 Monaten. Bereits auf Bitrise? L2 schrittweise auf einen Release-Knoten — kein Big-Bang nötig.
Buildkite Mac Agent: Stärken und Grenzen
Buildkite hält die Queue in der Cloud, Agenten auf Ihrer Hardware — gute Burst-Elastizität, klare Artifact-Aufbewahrung. Nachteil: zusätzliche Orchestrierung; für drei Macs wirkt es manchmal oversized. Der Kunde hat Buildkite PoC’d: Burst top, dennoch GitHub Actions nativ, weil YAML-Kompetenz nur dort sitzt. Mit bestehendem Buildkite gilt dieselbe fast/archive-Label-Strategie wie oben — Details im Buildkite-Cloud-Mac-Artikel.
Cloud Mac vs. lokale Mac-mini-CI
Acht Mac minis im eigenen Rack: hohe CapEx, Abschreibung, Stromausfall, Zertifizierungs-Audit. Drei Cloud Macs: planbare OpEx, Tages-PoC, Region nahe Git. Lokale CI lohnt bei >14 Stunden Compile/Tag pro Maschine über Jahre konstant. Cloud Mac CI passt zu schwankenden Peaks, Contractor-Wochen, Review-Saisons mit Notarisierungs-Engpass. Das Team behält zwei Büro-Macs für Entwicklung; schwere Xcode-CI läuft in der Cloud — statt acht minis, die meist idle sind.
Queue-SLO: vierte Maschine nur mit Daten, nicht Bauchgefühl
Empfohlene Metriken: queue_wait_seconds (P95), run_duration nach L0/L1/L2, cache_hit_ratio, l2_concurrent (selten > 3). Beispiel-Schwellen: L1 queue P95 < 8 Min.; L2 P95 < 25 Min. Erst wenn L2 drei Tage hintereinander über Schwellwert liegt und Cache-Treffer > 60 %, fourth Release-Knoten prüfen.
Caching: der Hebel für 63 Maschinenstunden
DerivedData-Key: branch + Xcode-Version; SPM/CocoaPods-Lock-Änderung bustet den Cache. Fast-Knoten teilen read-only Cache, Release-Knoten hält L2-DerivedData lokal auf NVMe. Signing-Material über Vault — nicht im Cache-Bundle. Keys müssen macOS/Xcode-Minor enthalten, sonst „Treffer, aber Link-Fehler“ nach Upgrade.
Minimale GitHub Actions Self-hosted Runner-Konfiguration
Drei Registrierungen: macos-fast ×2, macos-archive ×1. L2 braucht concurrency, damit Release-Jobs sich nicht gegenseitig canceln:
concurrency:
group: ios-archive-${{ github.ref }}
cancel-in-progress: false
jobs:
archive:
runs-on: [self-hosted, macos-archive]
steps:
- uses: actions/checkout@v4
- run: xcodebuild archive -scheme App -archivePath build/App.xcarchive
Release-Knoten: dedizierter macOS-User + Match; nach Reboot Unlock-Skript vor der Nacht-Queue. Details: xcodebuild-Dokumentation und Fastlane.
Release-Tag: wenn 500 Runs zu 650 werden
Reihenfolge: (1) nicht-kritisches L0 stoppen; (2) gehostete Runner nur für L1; (3) vierten Cloud Mac 48 Stunden tageweise dazu. L2 dauerhaft auf 2 parallel pro Maschine erhöhen zahlt man mit zufälligen Notarisierungs-Fehlschlägen.
Wann die vierte Maschine Pflicht wird
- L2 queue P95 eine Woche > 40 Min., Cache bereits optimiert.
- Monorepo mit >5 Apps an einem Release-Knoten, Nachtfenster reicht nicht.
- Jeder PR mit vollem Archive — zuerst Pipeline, nicht Hardware kaufen.
Anti-Patterns
Volles Archive pro PR; Mac Runner ohne Label-Trennung; zwei Archives auf einem 16-GB-M4; Cache-Keys ohne Branch; nur Success-Rate statt queue_wait — alles lässt drei Maschinen „zu wenig“ wirken und treibt falsche Acht-Mini-Bestellungen.
FAQ: kurze Antworten für Suche und Snippets
Wie viele Macs für 500 iOS-Builds pro Tag?
Mit Job-Schichtung (PR / Tests / Archive getrennt) und DerivedData-Cache reichen für die meisten Teams drei Apple-Silicon Cloud Macs: 2 × Fast Mac Runner + 1 × Release Mac Runner. Ist über die Hälfte der Runs volles Archive, Pipeline anpassen oder vierten Release-Knoten planen.
Wie viele Jobs parallel auf GitHub Actions Mac Runner?
Auf 16-GB-M4: 2 leichte Jobs (PR, Unit Test) oder 1 Archive-Job. Dauerhaft „2× Archive“ vermeiden.
Warum Archive nicht hoch parallel?
Speicherdruck → Swap, Disk-Queue, Keychain-Locks und codesign-Konflikte — sporadische Timeouts statt reproduzierbarer Compile-Fehler.
Ist Cloud Mac günstiger als Xcode Cloud?
Bei hochfrequentem iOS CI/CD, dauerhaftem Self-hosted Runner und residenten Secrets sind drei Cloud Macs meist planbarer als Minuten-Abrechnung. Seltene Releases, null Ops: Xcode Cloud zuerst testen.
Bitrise oder drei Cloud Macs?
Bitrise spart Ops, eignet sich für schnellen Start. Bei ~500 Jobs/Tag und eigener Queue-/Cache-Kontrolle gewinnt oft Self-hosted Mac Runner + Cloud Mac — niedrigere Jahreskosten, L2-Concurrency nach Ihren Regeln.
VPSSpark: Cloud-Mac-Baseline 2 Fast + 1 Release
Wer „500 Runs/Tag — wie viele Macs?“ rechnet: zwei Wochen Logs wie Abb. 1 aufschlüsseln, dann prüfen, ob 63 Maschinenstunden für Ihr Modell passen. VPSSpark Cloud Mac mini M4 / M4 Pro — Tages-PoC oder Abo — für GitHub Actions Self-hosted Mac Runner, PR und Archive in getrennten Queues.
Siehe Mac-Cloud-Tarife oder VPSSpark-Startseite — Region wählen, echten Workflow bucketen, dann entscheiden ob drei reichen — statt acht minis vorab zu bestellen.