iOS-CI/CD mit GitHub Actions von Grund auf einrichten
Das ist die ganze Pipeline für eine iOS-App mit GitHub Actions, in der Reihenfolge, in der Sie sie aufbauen. Erst kompilieren und testen. Dann signieren. Dann archivieren und hochladen. Jeder Schritt ist klein, und das YAML ist zum Kopieren bereit. Sie funktioniert auf gehosteten GitHub-Runnern und auf einem dedizierten Mac. Nur die Zeile runs-on ändert sich.
1. Das Gerüst
name: ios
on:
pull_request:
push:
branches: [main]
concurrency:
group: ios-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: macos-latest # or [self-hosted, macOS, your-label]
timeout-minutes: 30
steps:
- uses: actions/checkout@v42. Xcode auswählen
Gehostete Images bringen mehrere Xcode-Versionen mit. Wählen Sie eine ausdrücklich aus, damit ein Image-Update Ihren Build nicht unbemerkt verändert:
- run: sudo xcode-select -s /Applications/Xcode_26.6.app
- run: xcodebuild -versionAuf einem dedizierten Mac wird die Version einmal auf der Maschine festgelegt, und dieser Schritt entfällt.
3. Abhängigkeiten
- uses: actions/cache@v4
with:
path: |
~/Library/Caches/org.swift.swiftpm
Pods
key: ${{ runner.os }}-deps-${{ hashFiles('**/Package.resolved', '**/Podfile.lock') }}
- run: pod install --repo-update # CocoaPods projects only4. Bauen und im Simulator testen
- run: |
xcodebuild test \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 17' \
-resultBundlePath TestResults \
| xcpretty
- uses: actions/upload-artifact@v4
if: failure()
with: { name: test-results, path: TestResults }Für Pull Requests hören Sie hier auf. Alles Weitere ist Release-Arbeit und gehört nur auf main.
5. Code-Signierung ohne geteilte Mac-Anmeldung
Nutzen Sie einen App-Store-Connect-API-Schlüssel statt einer Apple ID. Speichern Sie Key ID, Issuer ID und den Inhalt der .p8-Datei als Repository-Secrets. Lassen Sie xcodebuild mit in der Cloud verwalteten Profilen signieren:
- run: echo "${{ secrets.ASC_KEY_P8 }}" > AuthKey.p8
- run: |
xcodebuild archive \
-scheme MyApp -configuration Release \
-archivePath build/MyApp.xcarchive \
-allowProvisioningUpdates \
-authenticationKeyPath "$PWD/AuthKey.p8" \
-authenticationKeyID "${{ secrets.ASC_KEY_ID }}" \
-authenticationKeyIssuerID "${{ secrets.ASC_ISSUER_ID }}"6. Exportieren und zu TestFlight hochladen
- run: |
xcodebuild -exportArchive \
-archivePath build/MyApp.xcarchive \
-exportPath build/export \
-exportOptionsPlist ExportOptions.plist \
-allowProvisioningUpdates \
-authenticationKeyPath "$PWD/AuthKey.p8" \
-authenticationKeyID "${{ secrets.ASC_KEY_ID }}" \
-authenticationKeyIssuerID "${{ secrets.ASC_ISSUER_ID }}"
- run: |
xcrun altool --upload-app -f build/export/MyApp.ipa -t ios \
--apiKey "${{ secrets.ASC_KEY_ID }}" --apiIssuer "${{ secrets.ASC_ISSUER_ID }}"Viele Teams packen die Schritte 5 und 6 in fastlane. Die Befehle darunter sind dieselben.
7. Wohin die Minuten gehen, und die Wahl des Runners
Auf einem gehosteten Runner wiederholen sich die Schritte 2 und 3 bei jedem Job und kosten etwa $0.062 bis $0.102 pro Minute. Auf einem dedizierten Mac passieren sie einmal, und die Caches bleiben warm. Deshalb verlegen Teams ab etwa 2,242 Minuten im Monat die ganze Pipeline auf eine Maschine zum Festpreis. Sie ändern nur die Zeile runs-on. Der Leitfaden zur Pipeline auf dedizierter Hardware behandelt diese Seite.
Häufige Fragen
Brauche ich einen Mac, um iOS-CI mit GitHub Actions zu betreiben?
+
Ja. Zum Bauen und Signieren von iOS-Apps braucht man Xcode, und Xcode läuft nur unter macOS. Der Build-Job braucht also einen macOS-Runner, entweder gehostet von GitHub oder einen Mac unter Ihrer Kontrolle.
Wie signiere ich eine iOS-App in der CI ohne meine Apple ID?
+
Erstellen Sie einen App-Store-Connect-API-Schlüssel. Speichern Sie ID, Issuer ID und den Inhalt der .p8-Datei als Secrets und übergeben Sie sie mit -allowProvisioningUpdates an xcodebuild. Auf dem Runner brauchen Sie weder Apple ID noch Keychain-Passwort.
Sollten Tests bei jedem Pull Request laufen?
+
Bauen und testen Sie bei jedem Pull Request gegen einen Simulator. Archivierung, Signierung und Upload zu TestFlight bleiben auf main. Das sind die langsamen, teuren Schritte.
Was ändert sich, wenn ich diese Pipeline auf einen dedizierten Mac verlege?
+
Die Zeile runs-on. Xcode-Auswahl und das Cachen von Abhängigkeiten werden überflüssig, weil die Maschine beides zwischen den Jobs behält. Und vor dem Build gibt es keine Warteschlange.