Sie sind im Dienst und nutzen mindestens zwei gemietete Cloud-Macs mit Fastlane für iOS-Releases — und sehen grüne Build-Logs, aber keinen neuen TestFlight-Build (oder das falsche Paket). Noch unentschieden wegen Cloud oder zweitem Mac? Zuerst TestFlight-Dedicated-Runner oder Xcode-Builds in die Cloud — hier gilt: ein Mac baut, einer lädt hoch.
Nach dem Lesen sollten Sie zwei Dinge können: ① aus Logs erkennen, ob Zertifikat, Artefakt oder Netzwerk das Problem ist; ② prüfen, ob die Rollen der beiden Macs stimmen und ob mehr Hardware wirklich hilft.
- Build-Maschine: kompiliert in Xcode und exportiert nur das Installationspaket (
.ipa) - Upload-Maschine: signiert mit dem Release-Zertifikat und lädt die
.ipazu TestFlight hoch; die in App Store Connect eingetragene Ausgangs-IP gilt nur für diesen Host - Archive / Build: Xcode-Ausgabe für Distribution; zwischen Rechenzentren nur fertige
.ipa, kein Projekt-Cache-Ordner - match: Fastlane-Tool für Release-Zertifikate — nur auf einem Host
Überspringen, wenn: Sie nur einen Mac haben oder TestFlight stabil läuft und Sie noch nicht in „Build + Upload“ getrennt haben. Bei mehreren Cloud-Macs und Pipeline-Problemen: weiterlesen.
- Zertifikatsgrenze — Release-Zertifikat wurde auf die Build-Maschine synchronisiert, Upload-Maschine kann nicht signieren (kritisch)
- Artefaktgrenze —
.xcarchivewird zwischen Rechenzentren verschoben statt einer prüfbaren.ipa(am leichtesten zu übersehen) - Netzwerk-Identitätsdrift — ASC-Whitelist zeigt noch die alte IPv4 des Upload-Hosts (am mühsamsten zu debuggen)
Kontext (1 Minute): Nach dem Zweit-Mac knallt meist die Grenze, nicht die Leistung
Viele Teams mieten zwei Cloud-Macs: einer nahe Asien kompiliert tagsüber, einer in Kanada liefert an Apple (TestFlight / App Store). Der zweite Mac soll Zeit sparen — aber im Dienst kosten beide Maschinen, die Zertifikate und Dateien anfassen, am meisten Zeit: Build grün, TestFlight ohne neue Version.
Zuerst die typische Fehlaufteilung (Modell B), dann unser Schnitt (Modell A) und die Verifikation.
Empfohlen: Modell A (Build nur Paket, Upload nur Veröffentlichung)
Kurz: Die Build-Maschine liefert nur .ipa; die Upload-Maschine besitzt Zertifikate und TestFlight. match nicht auf beiden — der Private Key landet auf dem falschen Host.
| Rolle | Build-Maschine (Hongkong) Example: APAC daytime builds | Upload-Maschine (Kanada) Fixed IP for Apple |
|---|---|---|
| Aufgabe | Code ziehen → Xcode-Build → .ipa exportieren | .ipa empfangen → Release-Signatur → TestFlight |
| Nicht | Kein match, kein TestFlight-Upload | Kein vollständiger Projekt-Build |
| Übergabe | Installationspaket + RUN_ID | Keine (Ziel: App Store Connect) |
Beide Runner laufen auf dedizierten Hashvps-Mac-minis — primär wegen fester IP + Rollenisolation (Upload-IP in ASC, Build-IP nicht). Hongkong-Spezifikationen: Mac-mini-Miete Hongkong. Vor dem horizontalen Skalieren von Build-Runnern zuerst die Upload-Identität in ASC absichern.
Beide Runner laufen auf dedizierten Hashvps-Mac-minis — primär wegen fester IP + Rollenisolation (Upload-IP in ASC, Build-IP nicht). Hongkong-Spezifikationen: Mac-mini-Miete Hongkong. Vor dem horizontalen Skalieren von Build-Runnern zuerst die Upload-Identität in ASC absichern.
Häufiger Fehler: Modell B (beide „helfen mit“)
Modell B heißt: Rollen nie getrennt — so lief es bei uns in Woche eins. Es sieht nach Aufteilung aus, ist aber:
- Beide führen
matchaus → Release-Private-Key bleibt auf der Build-Maschine → Upload kann nicht signieren → TestFlight scheitert dauernd - Build rsynct
.xcarchivedirekt → Upload exportiert erneut, anderer SHA als auf dem Build-Host → falsches Artefakt, Invalid Binary - Beide Hosts
export_ipa→ zwei driftendeExportOptions.plist→ Upload OK, Build nicht promotable
Trifft eine Zeile auf euren Workflow zu, hilft kein dritter Mac — ihr braucht den Build-/Upload-Role-Split (Modell A), nicht mehr Runner-Kapazität.
Observability: gleicher Build auf Build- und Upload-Seite nachweisen
Der häufigste Vorfall: „IPA von gestern hochgeladen“. Dieselben Felder in Build- und Upload-Logs:
RUN_ID(Pipeline-Lauf, z. B.hk-build-88421)GIT_SHA(kurzer Commit-Hash)- Artefaktpfad + SHA256 (in
build/manifest.json)
# Hongkong · build · build [15:11:42]: Successfully exported: ./build/MyApp-20260604-1502.ipa [15:11:42]: RUN_ID=hk-build-88421 GIT_SHA=a3f91c2 SHA256=9f2a… # Kanada · upload · upload [02:22:01]: RUN_ID=hk-build-88421 GIT_SHA=a3f91c2 ipa_sha256=9f2a… [02:24:18]: Successfully uploaded package to App Store Connect
Dienstregel: Fehlt im Upload-Log dieselbe RUN_ID, zuerst Artefaktfehler behandeln — nicht ASC oder Apple-Statusseiten.
30-Sekunden-Triage: kein neuer TestFlight-Build?
Nicht sofort das vollständige Log durchscrollen. Eine Ebene im Baum reicht, um das Kapitel zu finden (für Runbooks bookmarken):
Kein neuer TestFlight-Build?
├─ Upload RUN_ID ≠ Build RUN_ID (oder manifest fehlt)
│ → Fallstrick 2 · Artefaktgrenze
├─ Logs: match / AppStoreDistribution / code signing identity
│ → Fallstrick 1 · Zertifikatsgrenze
└─ Upload „erfolgreich“, ASC leer / 403 / Invalid Binary (Build alles grün)
→ Fallstrick 3 · Netzwerk-Identitätsdrift
| Muster | Log-Schlüsselwörter (grep) | Release-Auswirkung |
|---|---|---|
| Fall 1 Zertifikat | AppStoreDistribution, No matching provisioning, match failed |
Blocker: TestFlight / Store-Upload gestoppt |
| Fall 2 Artefakt | RUN_ID mismatch, Invalid Binary, export_method-Konflikt |
Hoch: falscher Build oder kein Promote |
| Fall 3 Identität | 403, Upload OK ohne processing, ASC-Netzwerklimits |
Intermittierend: wirkt wie „Zufall“ |
Drei Fehlermuster (ausführlich)
Fall 1: Zertifikatsgrenze falsch
30 Sekunden: Build-Archive grün; Upload rot, Fehler bei match / Signing.
Log: AppStoreDistribution, Could not find a matching code signing identity, No matching provisioning profiles.
Release: Blocker — TestFlight und App Store bis der Privatkey wieder nur auf dem Upload-Host liegt.
Symptom: Archiv auf dem Build-Runner erfolgreich, Upload-Lane scheitert; TestFlight ohne neuen Build.
Could not find a matching code signing identity for type 'AppStoreDistribution' ❌ Lane upload failed
Ursache: Modell B — Build-Runner führt noch match(appstore) aus, Distribution-Key nicht auf Upload.
Fix: Alle match-Aufrufe vom Build-Runner entfernen; Upload exklusiv (Rotate nur dort, eigenes Wartungsfenster). Siehe match readonly.
Fall 2: Artefaktgrenze falsch
30 Sekunden: RUN_ID und ipa_sha256 in Build- vs. Upload-Log vergleichen; oder ASC meldet Invalid Binary ohne Signing-Fehler auf dem Build-Host.
Log: RUN_ID ungleich, Invalid Binary, doppeltes export_ipa / ExportOptions auf zwei Hosts.
Release: hoch — alter Commit, falscher Build oder hängendes Processing; Pipeline kann noch grün wirken.
Ursache: .xcarchive zwischen RZ verschoben oder doppelt exportiert. Minimale Einheit zwischen RZ: IPA mit SHA, kein Archivordner.
Fix: Einmal export_ipa + manifest.json auf dem Build-Host; Upload nur noch upload_to_testflight, kein zweites Export.
Fall 3: Netzwerk-Identitätsdrift
30 Sekunden: Fastlane meldet Upload-Erfolg; Signing auf beiden Hosts grün; TestFlight leer oder ASC 403.
Log: 403, Successfully uploaded ohne neuen Build, ASC-Netzwerkablehnung trotz grüner Build-Logs.
Release: intermittierend — mal geht’s, mal nicht; frisst Dienstzeit.
Ursache: Upload-IPv4 ≠ Eintrag in der ASC-Whitelist (dritte, leise Fehlerklasse).
Fix: Täglich auf dem Upload-Host curl -4 ifconfig.me gegen ASC abgleichen; bei Drift Whitelist + Alert. Build-IPv4 gehört nicht in die ASC-Liste — sonst maskiert ein falscher Eintrag echte Upload-Drift.
So erkennt ihr, dass der Split wirklich sitzt
Checkliste statt verstreuter FAQ — eine Woche nach dem Rollout sollte jede Zeile ohne Diskussion „ja“ sein:
| Prüfpunkt | Erwartung |
|---|---|
| RUN_ID ausgerichtet | In Build-Export- und Upload-Logs identisch: RUN_ID, GIT_SHA, ipa_sha256 |
| Build ohne Distribution | Auf dem Build-Host in Keychain/Logs nie AppStoreDistribution oder match-Step |
| Upload nur Distribution | Upload-Lane: nur match (oder resign) + upload_to_testflight, kein ARCHIVE SUCCEEDED |
| Artefaktform | Zwischen RZ nur .ipa + manifest (< 200 MB); kein .xcarchive-rsync |
| ASC-Identität | Aktuelle Upload-IPv4 = ASC-Whitelist; Build-IP nicht in ASC |
| Runner-Isolation | Labels role=ios-build-hk und role=ios-upload-ca, Workflow-needs-Kette |
Upgrade-Einordnung: in welcher Stufe seid ihr?
Incident-Fix beantwortet „heute Nacht“; die Stufe beantwortet „nächste Woche Architektur ändern?“. Vier Stufen — halten, korrigieren oder Role-Split erzwingen. Viele Teams springen von Stufe 1 direkt zu zwei Macs ohne Labels und landen in Stufe 2; dieser Text ist die Brücke zu Stufe 3 mit messbarer Abnahme statt Bauchgefühl.
Stufe 1: Ein Mac, eine Fastlane
Merkmal: ein Runner; match, build, upload in einem Fastfile und einer Keychain. Zuerst Disk, Derived Data und GitHub-Actions-Abrechnung optimieren, dann erst einen zweiten Build-Knoten erwägen.
Fazit: kein Dual-Mac nötig. Nur APAC-Build langsam → Build-Knoten hinzufügen, Upload nicht vorziehen splitten.
Stufe 2: Zwei Macs, kein Role-Split (Modell B)
Merkmal: zwei oder mehr Runner; mehrere führen match / export / upload aus; Logs zeigen die drei Grenzfehler dieses Artikels. Oft entsteht das aus dem Wunsch, „jeder Mac soll etwas können“ — genau das erzeugt Zertifikats- und Artefakt-Drift, die in Stufe 3 mit klaren Labels und needs-Ketten verschwindet.
Fazit: ❌ Ein dritter Mac löst nichts, erhöht nur Zertifikats- und Artefakt-Drift. Nächster Schritt: Modell A, nicht mehr Runner.
Stufe 3: Role-Split (Modell A · Ziel dieses Textes)
Merkmal: Build/Upload getrennt; zwischen RZ nur IPA mit RUN_ID; match nur auf Upload; feste Upload-IP. In dieser Stufe lohnt Monitoring auf Drift der Upload-IPv4 und wöchentliche Stichproben der Abnahmetabelle — nicht nur Reaktion bei rotem TestFlight.
Fazit: ✔ Abnahmetabelle stabil grün → horizontal Build-Runner skalieren oder Upload-Host härten. Feste IPs und Knoten: Hashvps-Angebote (zuerst ASC-IP für Upload, dann Build erweitern).
Stufe 4: Mehrere Upload-Regionen (fortgeschritten)
Merkmal: Upload in US/EU/APAC; ASC-Keys und IP-Policy pro Region; Promotion und Compliance als eigene Linie.
Fazit: Distributionsplattform, nicht „noch ein CI-Mac“ — Artefaktregistry, Policy, Audit; außerhalb dieses Runbooks.
| Stufe | Jetzt sinnvoll |
|---|---|
| Stage 1 | Einzel-Mac halten; Build-/Upload-Fenster tunen |
| Stage 2 | Keine weiteren Macs; Modell A + Abnahmetabelle |
| Stage 3 | Build horizontal; Upload-Monitoring festziehen |
| Stage 4 | Eigenes Projekt: Multi-Region, nicht Fastlane-Patch |
Endurteil (nur dieser Block reicht zum Handeln)
Wenn alle drei Punkte zutreffen, entscheidet hier — ohne den Rest noch einmal zu lesen:
- Mindestens 2 Mac-Runner (oder äquivalente Multi-Node-CI)
- Fastlane-Kette mit instabilem TestFlight (
match failed, Processing hängt, sporadisch kein Build) matchlief bereits auf mehr als einem Host
👉 Keine weiteren Maschinen kaufen.
👉 Drei Schritte (heute startbar):
- Stoppen:
match/sync_code_signingauf allen Nicht-Upload-Knoten (Signatur nur Upload) - Erzwingen: zwischen RZ nur
.ipa+manifest.json(kein.xcarchive-rsync) - Vereinheitlichen:
RUN_ID+GIT_SHA+ipa_sha256vor Upload prüfen
Wenn das nicht geht: ihr seid in Stufe 2 (Modell B). Mehr Hardware vergrößert den Ausfall, behebt ihn nicht. Fastfile und Workflow-Labels zuerst, Abnahmetabelle grün, dann Build-Kapazität. Plant ein Wartungsfenster nur für den Upload-Host, wenn match rotiert werden muss — nie parallel auf dem Build-Runner.
lane :upload_only do |options| verify_run_id!(options) # manifest.json match(type: "appstore", readonly: true) upload_to_testflight(ipa: options[:ipa_path], api_key_path: "asc_api_key.json") end
Kurz gefragt, kurz beantwortet
Kann der Build-Runner ohne Distribution archivieren? Projektabhängig; wir exportieren auf dem Build-Host eine IPA für die finale App-Store-Signatur auf dem Upload-Host (oder Build nur Compile-Check, Upload resign). Entscheidend: match nur auf einem Host. Verteilt signing auf zwei Keychains ohne klare Rolle — das ist Modell B in Verkleidung.
match läuft bald ab? Rotate nur auf dem Upload-Host im Nicht-readonly-Fenster; Build-Runner bis Profile stabil pausieren.
Unterschied zum Reise-Runbook? Reise-Text = WLAN vor Ort; dieser Text = Rechenzentrum-Rollen und Fastlane-Grenzen — Abnahmetabelle 1:1 übernehmbar.
Serie: ① Runbook: Dual-Cloud-Mac + Fastlane (TestFlight leer / match failed / Multi-Mac-iOS-CI). Folge: ② Build·Sign·Distribute, ③ Modell-B-Anti-Muster; Einstieg TestFlight-Setup. Bookmarkt den 30-Sekunden-Baum für die nächste Nachtschicht.