VPSSpark Cloud Mac CI Serie #9. Eine Frage aus DACH-Flutter-Teams kommt ständig: Kann ein Team mit 8–15 Leuten die CI mit zwei Cloud Macs stabil betreiben? Ja — wenn Android und iOS getrennt gedacht werden: Android-Builds bleiben auf dem GitHub-gehosteten ubuntu-latest, auf macOS laufen zwei Self-hosted Runner in den Pools macos-fast und macos-archive, und die harte Regel gilt: kein Archive auf PR. Im Folgenden: Lastmodell, Job-Schichten, Workflow-Snippet und Go-live-Checkliste. Warteschlangen-Diagnose und Runner-Anbindung finden Sie in Serie #2 sowie im Praxisbericht drei Cloud Macs für hohes iOS-CI-Volumen.
1 · Überraschende Erkenntnis: Der Flutter-CI-Engpass liegt nicht bei Dart
Die meisten roten Flutter-CI-Läufe scheitern nicht an langsamem flutter test, sondern daran, dass iOS-Jobs die macOS-Runner-Slots blockieren — weil in PRs fälschlich flutter build ipa mitläuft.
Cross-Platform suggeriert oft „ein Workflow für alles“. In der Praxis laufen flutter analyze und Unit-Tests auch auf Linux; macOS brauchen Sie für CocoaPods, Xcode-Compile, Signing und IPA-Export. Mischt ein Archive-Job in den PR-Pfad, blockiert ein Cloud Mac 25–40 Minuten — alle anderen PRs stehen auf Queued. Das kennen native iOS-Teams; die Diagnose bleibt wait time >> run time (siehe macOS-Runner-Warteschlangen-Diagnose).
Zwei Cloud Macs sind daher keine „zwei Universal-Maschinen“, sondern eine bewusst getrennte Zwei-Pool-Topologie: eine für schnelles Feedback, eine für Release-Schwere. APK/AAB bleiben auf Linux — macOS-Slots nicht für Gradle verbrennen.
2 · Lastmodell: Wie viel macOS-Parallelität braucht ein 8–15-Personen-Team?
Als Referenz ein mittelaktives Flutter-Team (1–2 PRs pro Person und Tag, nightly auf main, wöchentliche Releases):
Im Fast-Pool (macos-fast): analyze, Tests, iOS-Simulator-Build — Ziel 8–14 Minuten Wall Time mit pub-/DerivedData-Cache. Im Archive-Pool (macos-archive): Release-IPA plus Notarisierung — 20–35 Minuten, aber selten (main, Tags, nightly). Gemischte Pools lassen Archive den Fast-Pool verstopfen; getrennt bleibt die P95-Wartezeit für PRs meist unter 10 Minuten — ausreichend für Code-Review-Rhythmus in Berlin, München oder Zürich.
Ab 15+ Leuten oder vielen Matrix-Flavors im Monorepo lohnt eine dritte Archive-Redundanz — Kapazitätsplanung wie im 500-Builds/Tag-Szenario, nicht Gegenstand dieses Zwei-Maschinen-Starts.
| Plattform | Runner | Typischer Job | Im PR? |
|---|---|---|---|
| Android | ubuntu-latest (gehostet) |
flutter build apk/appbundle, Android-Tests |
Ja |
| iOS Fast Feedback | Cloud Mac #1 · macos-fast |
analyze, Tests, build ios --simulator |
Ja |
| iOS Release | Cloud Mac #2 · macos-archive |
build ipa, Notarisierung, TestFlight |
Nein |
3 · Drei Flutter-Job-Ebenen: CI Hard Rules
Flutter-Pipeline auf L0/L1/L2 mappen — passend zum On-call-Handbuch:
| Ebene | Flutter-Inhalt | Pool | PR-Trigger? |
|---|---|---|---|
| L0 | dart format --set-exit-if-changed, flutter analyze, leichte Tests |
Linux oder macos-fast |
Ja |
| L1 | Integrationstests, flutter build ios --simulator, Widget-Tests |
macos-fast |
Ja · kein Archive |
| L2 | flutter build ipa, App Store Connect, Notarisierung |
macos-archive |
Nein · nur main/tag/schedule |
Drei Regeln wie bei nativem iOS: Rule 1 PR ohne L2; Rule 2 L2 nur im isolierten Pool; Rule 3 Fast-Pool darf nicht durch Archive blockiert werden. Flutter-spezifisch: vieles in L0 läuft auf Linux — wer alles an macos-fast bindet, verschwendet macOS-Kapazität. PR: Linux- und macOS-Jobs parallel; macOS nur für „nicht auf Linux machbar“.
4 · Zwei-Maschinen-Topologie: Aufgaben von Cloud Mac #1 und #2
Abb. 1 · Typische Topologie: zwei Cloud Macs für Flutter-Team-CI
macos-fastL0/L1 · 8–14 min/Jobmacos-archiveL2 · nur main/tagCloud Mac #1 (Fast-Pool): Labels [self-hosted, macOS, macos-fast, flutter]. Flutter SDK fixiert (fvm oder Image-Pin), Xcode, CocoaPods. Runner als Dauer-Service mit Autostart; pub cache und DerivedData aggressiv warm halten — kurze Jobs, hoher Durchsatz.
Cloud Mac #2 (Archive-Pool): Labels [self-hosted, macOS, macos-archive, flutter-release]. Physisch getrennt — nie zwei Pool-Runner auf einem Mac (sonst ist Rule 2 wirkungslos). Distribution-Zertifikate und App Store Connect API Key im Keychain; Anbindungsschritte analog Runner-Onboarding in der Praxis. Vor Release: flutter doctor -v und Dry-run-Archive — Fast-Pool nicht kontaminieren.
Gleiche Region, gleiche Xcode-Hauptversion — weniger „Fast grün, Archive rot“. Cloud Mac: festes Image, stabile GitHub-Anbindung, kein Büro-UPS — gut für 7×24-CI. Zur Sicherheitsgrenze Self-hosted Runner: Archive-Maschine auf ein Repo oder eine Org beschränken.
5 · Workflow-Beispiel: PR und main getrennt
Kern der Aufteilung (vollständige Pipeline braucht noch Cache, Secrets, Artifacts). Flutter-CD-Dokumentation zu iOS-Signing; Zertifikate im Archive-Keychain vorausgesetzt.
jobs: android-pr: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - run: flutter analyze && flutter test - run: flutter build appbundle --release ios-pr-fast: if: github.event_name == 'pull_request' runs-on: [self-hosted, macOS, macos-fast, flutter] steps: - run: flutter analyze - run: flutter test - run: flutter build ios --simulator --no-codesign ios-release: if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: [self-hosted, macOS, macos-archive, flutter-release] steps: - run: flutter build ipa --export-options-plist=ExportOptions.plist # TestFlight / Notarisierung — nur L2
In ios-pr-fast fehlt bewusst build ipa. Geräte-Build auf PR: nur per workflow_dispatch, weiterhin Archive-Pool — nicht als Standard-PR-Event. Mit concurrency in der Workflow-Syntax alte PR-Runs abbrechen — oft ~20 % weniger sinnlose Wartezeit.
6 · Cache-Checkliste: vier Verzeichnisse für Flutter-Teams
Zwei Cloud Macs laufen stabiler mit gutem Cache als mit der neuesten CPU. Im Runner-Home persistieren (oder nightly Snapshot):
PUB_CACHE/~/.pub-cache— Dart-Deps; beipubspec.lock-Wechsel inkrementell- Flutter-SDK-Verzeichnis —
fvmpinnt Version; keinflutter upgradein CI ios/Pods+ CocoaPods-Cache —pod installist oft der iOS-Schwanz- Xcode DerivedData — hoher Nutzen bei gleichem
ios/Podfile.lock
Fast- und Archive-Pool kein gemeinsames DerivedData — Debug/Simulator und Release/Archive vermischen erzeugt Linker-Geisterfehler. Gradle-Cache für Android auf ubuntu-latest via Actions Cache; Cloud-Mac-Disk schonen.
pod repo update — zuerst auf Archive validieren, dann Fast-Pool. Tägliche PRs nur Cache; kein flutter pub upgrade als CI-Default.
7 · Go-live: von null auf Zwei-Maschinen-Anbindung
In 1–2 Werktagen PoC-fähig:
- 2 gleich spezifizierte Cloud Macs (M4 + 16 GB empfohlen; iOS-Compile-Spitzen oft 12 GB+)
- #1: Flutter/Xcode/CocoaPods, Runner
macos-fast; #2: Signing-Material, Runnermacos-archive - Workflows splitten: Android
ubuntu-latest; PR nur L1; main/tag L2 - pub/DerivedData/Pods persistieren; 3 Tage P95 wait < 10 min beobachten
- Bleibt
wait >> run: zuerst L2 in PRs prüfen, dann Maschinen addieren
PoC mit einer Cloud Mac für Fast-Pool; Archive vorübergehend auf gehostetem macos-latest (Wartezeit akzeptieren) — Logik validieren, dann zweite Maschine für Release-SLA. Widerspricht nicht „zwei Maschinen“: die zweite sichert Releases, nicht PR-Feedback.
8 · FAQ
Alle Flutter-Tests auf Linux — reicht ein Cloud Mac?
Ohne macOS-Compile im PR theoretisch ja — die meisten Teams fahren build ios --simulator für Native-Plugins. Ein Mac für L1 geht; Archive muss vom Fast-Feedback isoliert bleiben, sonst blockieren Releases alle PRs. Zwei Maschinen bleiben die sichere Wahl.
Zwei Cloud Macs vs. zwei Mac minis im Büro?
Topologie identisch; Unterschied im Betrieb: Cloud Mac ohne Hardware-Beschaffung, snapshotbare Images, oft stabilere GitHub-Bandbreite. Büro-Mac bei 7×24 und Serverraum; verteilte DACH-Teams wählen häufiger Cloud Mac — siehe auch Skalierung bei hohem Job-Volumen.
Codemagic oder gehostetes GitHub-macOS behalten?
Als Archive-Backup oder PR-Overflow. Nach Self-hosting-Hauptpfad lohnt gehostetes macOS bei < 4 Releases/Monat; aktive Flutter-Teams sparen langfristig oft mit eigenen Runnern.
Monorepo mit mehreren Apps — Job-Zählung?
Matrix × Flavors multiplizieren. Mehr als vier macOS-Jobs pro PR: Fast-Pool erweitern oder Matrix straffen — nicht Archive in PRs schieben.
Zwei Cloud Macs — Flutter-CI für Android und iOS sauber getrennt
Android bleibt auf Linux; iOS-Compile, Signing und IPA-Export brauchen macOS. Zwei Cloud Mac mini M4 mit macos-fast und macos-archive halten PR-Feedback und Release-Jobs auseinander — günstiger als Archive in jeden PR und dann für alle Queued. Apple Silicon beschleunigt flutter build ios und Xcode-Link; ~4 W im Leerlauf passen zu dauerhaftem 7×24-CI.
Gegenüber Büro-Mac-Raum: Cloud Mac per Tarif, klonbare Images, stabile GitHub-Anbindung — ideal für verteilte Flutter-Teams in DACH. Unix-native für Flutter, CocoaPods, Fastlane ohne Extra-Virtualisierung.
Für die iOS-CI-Migration Ihres Flutter-Teams: Start mit zwei VPSSpark Cloud Mac mini M4 als Minimal-Topologie — Tarife ansehen, Fast-Pool-PoC, danach Archive-Pool für Releases.