Angebot

Warum ist xcodebuild in der CI 2–3× langsamer als lokal?

CI Flutter iOS CI
2026-06-08 ~12 Min.

Diese Anleitung zeigt Flutter iOS Teams, wie sie den tatsächlichen Zeitverlust in CI sichtbar machen und mit reproduzierbarer Runner-/Cache-Strategie beheben.

Flutter iOS CI: Zeitvergleich lokal gegen CI
Lokal wirkt der Build normal, in GitHub Actions startet die Pipeline aber oft kalt und zieht die Gesamtzeit rund um xcodebuild auf das 2-3x Niveau.

Was zuerst wichtig ist

  1. Nicht nur xcodebuild ist langsam; entscheidend ist der Unterschied zwischen Cache- und Maschinen-Lebenszyklus.
  2. GitHub-hosted Jobs starten kalt, ein Self-hosted Runner bleibt wie eine warme Arbeitsmaschine.
  3. Persistente DerivedData- und CocoaPods-Caches bringen meist den größten Zeitgewinn.
  4. Disk- und Signing-Overhead wird oft fälschlich als Compilerproblem gelesen.
  5. Für Flutter iOS ist „Mac mieten“ für 48h Profiling sinnvoll, danach Self-hosted monatlich.

Lokaler Mac vs GitHub Actions: wo die Zusatzminuten entstehen

Lokal vergleichen viele Teams unter diesen Bedingungen:

  • Lokal: persistente SSD + warmer Toolchain-State; CI: ephemeres VM-Image + kalte Abhängigkeiten.
  • Lokal: bestehender Signing-Kontext; CI: neuer Keychain-Init pro Lauf.
  • Lokal: wiederholte Builds auf demselben Host; CI: mehr Branch-bedingte Cache-Misses.

In der CI-Pipeline sieht es typischerweise so aus:

  • Jeder Lauf startet mit einem sauberen Workspace.
  • flutter build ipa oder xcodebuild archive läuft in Release.
  • Auf gehosteten Runnern bleiben Caches selten warm — effektiv Cold Start.

Viele Teams vergleichen nur `xcodebuild archive`. In der Realität summieren sich Checkout, pod install, Keychain-Setup und DerivedData-Invalidierung. Genau daraus entsteht oft die 2-3x Wahrnehmung.

Fünf Gründe, warum xcodebuild in CI 2-3x langsamer wird

Diese Muster sehen wir regelmäßig in Flutter iOS Pipelines auf GitHub Actions und bei Migrationen auf Self-hosted Runner.

  1. 1) Zustandslose Runner resetten alle Dependency-Layer GitHub-hosted VMs werden pro Job neu aufgebaut. SPM-Checkout, Pods-Effekte und Modulcache bleiben nicht stabil warm.
  2. 2) Variable DerivedData-Pfade zerstören Inkrementalität Bei wechselnden Pfaden kann xcodebuild vorhandene Artefakte nicht sauber wiederverwenden und kompiliert unnötig neu.
  3. 3) Flutter-Plugin-Auflösung triggert kaltes Pod-Verhalten Bei lockfile-Drift kostet `pod install` schnell mehrere Minuten. Deterministische Dependency-Regeln sind hier zentral.
  4. 4) Signing- und Keychain-Schritte addieren serielle Latenz Zertifikatsimport, Keychain-Erstellung und Entsperren passieren in CI wiederholt und kosten fix Zeit.
  5. 5) Disk- und Inode-Druck bremst Schreibpfade Nicht nur CPU zählt. Wenig freier SSD-Platz und Inode-Engpässe verlangsamen Indexing und Build-Outputs deutlich.

Stage-Timing-Tabelle (Flutter iOS Beispiel)

Nutzt dieses Format für mindestens zehn Läufe, bevor ihr Provider oder Architektur wechselt.

Stage Lokaler Mac GitHub Actions Typische Ursache
flutter pub get0m25s1m10spub Cache nicht warm
pod install1m30s4m45skalte Specs + Plugin-Auflösung
xcodebuild compile/archive6m40s12m20sDerivedData-Mismatch
codesign/export0m55s2m00sKeychain-Bootstrap je Lauf
gesamt9m30s20m15sca. 2.1x langsamer

Bevor ihr xcodebuild beschuldigt

Bevor Sie xcodebuild optimieren, prüfen Sie pod install. CocoaPods ist in CI meist aus drei Gründen langsam:

  • Podfile.lock nicht committed — Auflösung wird unvorhersehbar.
  • Kein Cache für ios/Pods — Artefakte verschwinden nach dem Job.
  • Zusätzliches pod repo update — mit Lockfile meist unnötig.

Nutzen Sie in CI pod install --deployment und halten Sie Pods auf demselben SSD-Pfad. Self-hosted Mac mini spart Upload/Download von GitHub-Caches.

DerivedData, Pods und Cache: praxistaugliche Architektur

In Flutter iOS liefert ein stabiler absoluter DerivedData-Pfad plus konsistente Pod-Caches oft den größten Hebel. Mit Self-hosted Runner wird CI von wegwerfbar zu dauerhaft optimierbar.

Auch auf GitHub-hosted kann deterministische Pfad- und Lockfile-Politik helfen. Wenn möglich, bringt ein gemieteter Apple-Silicon-Host als Self-hosted Runner meist lokalähnliches Warmverhalten.

  • Fester derivedDataPath pro Target statt temporärer Pfade.
  • `pod install --deployment` gegen Lockfile-Drift.
  • Freie Disk + Inode als feste CI-Gesundheitsmetriken.

Fix-Muster: stabile Pfade + deterministische Dependency-Restore

Der folgende Ausschnitt ist bewusst robust statt trickreich. Ziel ist reproduzierbares Verhalten in Teams.

  1. concurrency im Workflow — alte Builds derselben Branch abbrechen.
  2. Tests auf Linux — keine Mac-Minuten für flutter test.
  3. Pods + DerivedData persistieren — feste Pfade self-hosted, feine Cache-Keys hosted.
  4. Nur den Shipping-Flavor bauen — nicht sechs Flavors scannen.
  5. Xcode/Flutter-Version pinnen — kein upgrade in CI.
# .github/workflows/ios-ci.yml
jobs:
  ios:
    runs-on: [self-hosted, macOS, ARM64, flutter-ios]
    steps:
      - uses: actions/checkout@v4
      - name: Restore caches
        run: |
          mkdir -p "$HOME/Library/Caches/CocoaPods"
          mkdir -p "$HOME/.pub-cache"
      - name: Build with stable DerivedData path
        run: |
          flutter pub get
          cd ios
          xcodebuild             -workspace Runner.xcworkspace             -scheme Runner             -configuration Release             -destination 'generic/platform=iOS'             -derivedDataPath "$HOME/ci-derived/runner"             CODE_SIGNING_ALLOWED=NO
      - name: Keep pod install deterministic
        run: |
          cd ios
          pod install --deployment

Damit sind oft 30-50% Zeitgewinn möglich. Danach kann ein dedizierter Self-hosted Runner-Pool weitere Stabilität bringen.

Pre-Merge-Checklist gegen Performance-Regressionen

Nach Flutter- oder Xcode-Updates sollte diese Liste Standard sein.

  • ☐ CI baut Release/IPA, lokal vergleichen Sie Debug
  • ☐ Job führt flutter clean oder löscht DerivedData
  • pod install zeigt jedes Mal viele Installing-Zeilen
  • ☐ GitHub-hosted Runner ohne lokale SSD-Caches
  • ☐ 16-GB-Maschine mit mehreren Jobs oder Simulatoren
  • ☐ README-Änderung triggert vollen iOS-Build
  • ☐ Xcode-Version in CI weicht vom lokalen Mac ab

Erste drei Punkte: Caches fixen. Letzte zwei: Trigger und Maschinengröße. Bei langsamer werdenden Builds auch Speicher prüfen.

FAQ

Ist GitHub Actions immer langsamer als lokal?

Nicht immer, aber bei Flutter iOS häufig, wenn Caches nicht warm bleiben. Self-hosted Runner schließen diese Lücke oft deutlich.

Direkt auf Self-hosted wechseln?

Erst messen. Wenn Kaltstartkosten dominieren, eine Pipeline auf Self-hosted spiegeln und Median aus zehn Läufen vergleichen.

Braucht man dafür teure Hardware?

Meist nicht. „Mac mieten“ auf Apple Silicon mit persistenten Caches reicht oft für spürbare Verbesserungen.

Wie überzeugt man das Team?

Stage-Zeiten in Wartekosten übersetzen und zusätzlich Release-Vorhersagbarkeit als Business-Wert zeigen.

Wenn ihr Flutter iOS CI neu aufsetzt, lest diese Seiten in der Reihenfolge.

Stabile iOS CI Geschwindigkeit auf echter Apple-Silicon-Hardware

kvmboot Cloud Mac bietet dedizierte M-Serien Hosts für Flutter iOS. Erst Mac mieten (täglich) zum Profiling, dann monatlicher Self-hosted Runner.

Wenn ihr CI-Varianz gegen planbare Releases tauschen wollt, Cloud-Mac-Tarife ansehen