Automatische iOS Builds mit GitHub Actions und Match
Das Einrichten von CI/CD für Capacitor-Apps kann komplex und zeitaufwendig sein. Hier ist das Wichtigste:
Empfohlen für neue Builds: Nutze Capgo Build
Wir empfehlen jetzt die Nutzung von Capgo Build mit Capgo CLI für native Capacitor Builds. Dieser Fastlane Match Guide ist für Teams gedacht, die bestehende GitHub Actions Pipelines pflegen. Neue iOS Builds sollten jedoch Capgo CLI nutzen, damit ihr Fastlane, Match-Repos, Xcode Runner, Zertifikate und Upload-Skripte nicht selbst verwalten müsst.
Capgo Build für CI/CD von Capgo
Spart die Wartung von Fastlane Match, Xcode Runner, Zertifikaten, Provisioning Profiles und Upload-Skripten. Capgo Build führt signierte native iOS Builds aus deiner bestehenden CI/CD Pipeline aus:
- Funktioniert mit deiner Pipeline: Trigger Capgo Build via GitHub Actions, GitLab CI, Jenkins oder lokale Skripte nach deinem Web-Build und
npx cap sync. - Signing via CI Secrets: Behalte App Store Connect Keys, Zertifikate, Provisioning Profiles, Passwörter und Team IDs in deinen eigenen CI Secrets.
- Kein Wartungsaufwand für native RunnerCapgo Build bietet gewartete Apple-Buildumgebungen, sodass du dich nicht um macOS-Runner, Xcode-Images, Fastlane oder Match-Repositories kümmern musst.
- Artefakte und Einreichung: Lade signierte Artefakte für QA herunter oder reiche Release-Builds über Capgo CLI ein.
Preise
- Capgo-Pläne ab 12 $/Monat
- Inklusive OTA-Updates und ca. 15 Native Builds pro Monat
- Zusätzliche Build-Minuten werden minutengenau über Credits abgerechnet
Capgo Build in CI/CD einrichten
Manuelle Setup-Anleitung
Das müssen Sie tun:
Continuous Delivery für iOS mit Fastlane und GitHub Actions mit match
Voraussetzungen
Bevor wir mit dem Tutorial fortfahren…
- Stelle sicher, dass Fastlane installiert ist auf deiner Entwicklungsmaschine.
- Mitgliedschaft im iOS Developer Program.
- Lust zu lesen? 😆…
- Ein Team aus vielen Entwicklern, ansonsten empfehlen wir die Nutzung von fastlane cert für einfachere Workflows.
Wichtige Preisinfos

https://github.com/features/actions
Der Service ist bis zum Limit „kostenlos“, abhängig von der gewählten Maschine.
Wir werden eine macOS Maschine nutzen; im Screenshot siehst du den Preis und die Limits (Preise zum Zeitpunkt der Tutorial-Erstellung, Änderungen vorbehalten).
🔴 Nachdem Anforderungen und Preise geklärt sind, machen wir weiter, wenn du möchtest…
📣 Im Post gehen wir davon aus, dass die App in iTunes Connect erstellt wurde und die Apple-Zertifikate vorliegen – Fastlane erledigt den Rest!
Abtauchen 🤿
Schritte in diesem Post
- App Store Connect API mit Fastlane Match nutzen
- Anforderungen
- App Store Connect API Key erstellen
- Einen App Store Connect API Key verwenden
- Fastlane-Dateien kopieren
- Fastlane match konfigurieren
1. App Store Connect API mit Fastlane match nutzen
Seit Februar 2021 ist für alle Nutzer eine Zwei-Faktor-Authentifizierung oder eine Zwei-Schritt-Verifizierung erforderlich, um sich bei App Store Connect anzumelden. Diese zusätzliche Sicherheitsebene für Ihre Apple ID hilft sicherzustellen, dass nur Sie auf Ihr Konto zugreifen können.
Von Apple Support
Um mit match zu starten, müssen Sie Ihre bestehenden Zertifikate widerrufen. Aber keine Sorge, Sie erhalten direkt neue.
Anforderungen
Um App Store Connect API nutzen zu können, benötigt Fastlane drei Dinge.
- Issuer-ID.
- Key-ID.
- Key-Datei oder Key-Inhalt.
Erstellen eines App Store Connect API Keys
Um Keys zu generieren, benötigst du Admin-Rechte in App Store Connect. Falls du diese Berechtigung nicht hast, leite diesen Artikel an die zuständige Person weiter und folge diesen Schritten.
1 — Logge dich bei App Store Connect ein.
2 — Auswählen Benutzer und Zugriff.

3 — Wähle den Tab Integration.

4 — Klicke auf Generate API Key oder den Add (+)-Button.

5 — Gib einen Namen für den Key ein. Der Name dient nur deiner Referenz und ist nicht Teil des Keys selbst.

6 — Wähle unter Access die Rolle für den Key aus. Die Rollen für Keys entsprechen den Rollen deiner Teammitglieder. Siehe RollenberechtigungenWir empfehlen die Auswahl von App-Manager.
7 — Klicke auf Generate.
Der Zugriff eines API Keys kann nicht auf spezifische Apps beschränkt werden.
Der Name des neuen Keys, die Key ID, ein Download-Link und weitere Infos werden auf der Seite angezeigt.

Hier findest du alle drei benötigten Informationen.
<1> Issue-ID.
<2> Key-ID.
<3> Klicke auf „Download API Key“, um deinen API Private Key herunterzuladen. Der Download-Link erscheint nur, wenn der Private Key noch nicht heruntergeladen wurde. Apple speichert keine Kopie des Private Keys. Du kannst ihn also nur einmal herunterladen.
🔴 Speichere deinen Private Key an einem sicheren Ort. Du solltest deine Keys niemals teilen, sie nicht in einem code-Repository speichern oder in clientseitigem code einbinden.
Einen App Store Connect API Key verwenden
Die API Key-Datei (die p8-Datei, die du herunterlädst), die Key ID und die Issuer ID brauchst du, um das JWT-Token für die Autorisierung zu erstellen. Es gibt mehrere Wege, diese Informationen über die neue Fastlane-Action an Fastlane zu übergeben, app_store_connect_api_key. Weitere Methoden findest du in der Fastlane-Dokumentation. Ich zeige diese Methode, weil sie meiner Meinung nach am einfachsten mit den meisten CI-Systemen funktioniert, bei denen man Umgebungsvariablen setzen kann.
Jetzt können wir Fastlane mit dem App Store Connect API Key verwalten, super!
2. Fastlane-Dateien kopieren
Fastlane ist eine Ruby-Library, mit der sich gängige Aufgaben in der Mobile-Entwicklung automatisieren lassen. Mit Fastlane kannst du eigene „Lanes“ konfigurieren, die eine Reihe von „Actions“ bündeln, um Aufgaben auszuführen, die du normalerweise in Android Studio erledigst. Mit Fastlane lässt sich viel erreichen, aber in diesem Tutorial nutzen wir nur eine Handvoll Core-Actions.
Erstelle einen Fastlane-Ordner in deinem Projekt-Root und kopiere die folgenden Dateien hinein: Fastfile
default_platform(:ios)
DEVELOPER_APP_IDENTIFIER = ENV["DEVELOPER_APP_IDENTIFIER"]
DEVELOPER_APP_ID = ENV["DEVELOPER_APP_ID"]
PROVISIONING_PROFILE_SPECIFIER = ENV["PROVISIONING_PROFILE_SPECIFIER"]
TEMP_KEYCHAIN_USER = ENV["TEMP_KEYCHAIN_USER"]
TEMP_KEYCHAIN_PASSWORD = ENV["TEMP_KEYCHAIN_PASSWORD"]
APPLE_ISSUER_ID = ENV["APPLE_ISSUER_ID"]
APPLE_KEY_ID = ENV["APPLE_KEY_ID"]
APPLE_KEY_CONTENT = ENV["APPLE_KEY_CONTENT"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
def delete_temp_keychain(name)
delete_keychain(
name: name
) if File.exist? File.expand_path("~/Library/Keychains/#{name}-db")
end
def create_temp_keychain(name, password)
create_keychain(
name: name,
password: password,
unlock: false,
timeout: 0
)
end
def ensure_temp_keychain(name, password)
delete_temp_keychain(name)
create_temp_keychain(name, password)
end
platform :ios do
lane :build do
build_app(
configuration: "Release",
workspace: "./ios/App/App.xcworkspace",
scheme: "App",
export_method: "app-store",
export_options: {
provisioningProfiles: {
DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
}
}
)
end
lane :refresh_profiles do
match(
type: "development",
force: true)
match(
type: "adhoc",
force: true)
end
desc "Register new device"
lane :register_new_device do |options|
device_name = prompt(text: "Enter the device name: ")
device_udid = prompt(text: "Enter the device UDID: ")
device_hash = {}
device_hash[device_name] = device_udid
register_devices(
devices: device_hash
)
refresh_profiles
end
lane :closed_beta do
keychain_name = TEMP_KEYCHAIN_USER
keychain_password = TEMP_KEYCHAIN_PASSWORD
ensure_temp_keychain(keychain_name, keychain_password)
api_key = app_store_connect_api_key(
key_id: APPLE_KEY_ID,
issuer_id: APPLE_ISSUER_ID,
key_content: APPLE_KEY_CONTENT,
duration: 1200,
in_house: false
)
match(
type: 'appstore',
git_basic_authorization: Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"),
readonly: true,
keychain_name: keychain_name,
keychain_password: keychain_password,
api_key: api_key
)
gym(
configuration: "Release",
workspace: "./ios/App/App.xcworkspace",
scheme: "App",
export_method: "app-store",
export_options: {
provisioningProfiles: {
DEVELOPER_APP_ID => "#{PROVISIONING_PROFILE_SPECIFIER}"
}
}
)
pilot(
apple_id: "#{DEVELOPER_APP_ID}",
app_identifier: "#{DEVELOPER_APP_IDENTIFIER}",
skip_waiting_for_build_processing: true,
skip_submission: true,
distribute_external: false,
notify_external_testers: false,
ipa: "./App.ipa"
)
delete_temp_keychain(keychain_name)
end
lane :submit_review do
version = ''
Dir.chdir("..") do
file = File.read("package.json")
data = JSON.parse(file)
version = data["version"]
end
deliver(
app_version: version,
submit_for_review: true,
automatic_release: true,
force: true, # Skip HTMl report verification
skip_metadata: false,
skip_screenshots: false,
skip_binary_upload: true
)
end
end
Appfile
app_identifier(ENV["DEVELOPER_APP_IDENTIFIER"])
apple_id(ENV["FASTLANE_APPLE_ID"])
itc_team_id(ENV["APP_STORE_CONNECT_TEAM_ID"])
team_id(ENV["DEVELOPER_PORTAL_TEAM_ID"])
Fastlane match konfigurieren
Fastlane match ist ein neuer Ansatz für das code Signing unter iOS. Fastlane match erleichtert Teams die Verwaltung der benötigten Zertifikate und Provisioning Profiles für iOS-Apps.
Erstelle ein neues privates Repository namens certificates, zum Beispiel in deinem GitHub Personal Account oder deiner Organisation.
Initialisiere Fastlane match für deine iOS-App.
fastlane match init
Wähle dann Option #1 (Git Storage).
[01:00:00]: fastlane match supports multiple storage modes, please select the one you want to use:1. git2. google_cloud3. s3?
Gib die URL des neu erstellten Repositories an.
[01:00:00]: Please create a new, private git repository to store the certificates and profiles there[01:00:00]: URL of the Git Repo: <YOUR_CERTIFICATES_REPO_URL>
Du findest nun im Fastlane-Ordner eine Datei namens Matchfile und
_git_url_muss die HTTPS-URL des Zertifikats-Repos enthalten. Optional ist auch SSH möglich, erfordert jedoch einen zusätzlichen Schritt.
# ios/Matchfilegit_url("https://github.com/gitusername/certificates")storage_mode("git")type("appstore")
Als Nächstes generieren wir die Zertifikate und geben bei der Abfrage von Fastlane Match deine Zugangsdaten ein.
Du wirst aufgefordert, eine Passphrase festzulegen. Merke sie dir gut, da GitHub Actions sie später zum Entschlüsseln deines Zertifikats-Repos benötigen.
fastlane match appstore
Wenn alles geklappt hat, sollte es etwa so aussehen:
[01:40:52]: All required keys, certificates and provisioning profiles are installed 🙌
Falls es Probleme mit GitHub und den erforderlichen Berechtigungen gab, hilft dir vielleicht dieser Post bei der Generierung von Git-Auth-Tokens weiter.
Die generierten Zertifikate und Provisioning Profiles werden in das Zertifikats-Repo hochgeladen.

Zuletzt öffne dein project in Xcode und aktualisiere das Provisioning Profile für die Release-Konfiguration deiner App.

Ein paar Hinweise 💡
MATCH
Damit die CI/CD die Zertifikate und Provisioning Profiles importieren kann, benötigt sie Zugriff auf das Zertifikate-Repository. Erstelle dazu einen Personal Access Token mit Leserechten für private Repositories.
Gehe in GitHub zu Einstellungen → Entwickler-Einstellungen → Personal Access Tokens → klicke auf Generate New Token → wähle den repo Scope aus → und klicke auf Generate token.

Erstelle eine Kopie des generierten Personal Access Tokens. Du benötigst ihn später für die Umgebungsvariable. GIT_TOKEN.
Ersetze dann deine generierte Match-Datei im Fastlane-Ordner durch Matchfile
CERTIFICATE_STORE_URL = ENV["CERTIFICATE_STORE_URL"]
GIT_USERNAME = ENV["GIT_USERNAME"]
GIT_TOKEN = ENV["GIT_TOKEN"]
FASTLANE_APPLE_ID = ENV["FASTLANE_APPLE_ID"]
git_url(CERTIFICATE_STORE_URL)
storage_mode("git")
type("appstore")
git_basic_authorization(Base64.strict_encode64("#{GIT_USERNAME}:#{GIT_TOKEN}"))
username(FASTLANE_APPLE_ID)
Dies wird von GitHub Actions genutzt, um Zertifikate und Provisioning Profiles zu importieren. Die Variable wird in GitHub Secrets gesetzt, statt sie hart im Code zu hinterlegen.
Build-Verarbeitung
In GitHub Actions erfolgt die Abrechnung basierend auf den Minuten, die du für deinen CI/CD-Workflow verbraucht hast. Erfahrungsgemäß dauert es etwa 10–15 Minuten, bis ein Build in App Store Connect verarbeitet werden kann.
Bei privaten Projekten können die geschätzten Kosten pro Build bis zu $0,08/Min. x 15 Min. = $1,2betragen, oder mehr, je nach Konfiguration oder Abhängigkeiten deines Projekts.
Wenn du bei privaten Projekten die gleichen Bedenken bezüglich der Kosten hast wie ich, kannst du den skip_waiting_for_build_processing zu true.
Wo ist der Haken? Du musst die Compliance deiner App in App Store Connect manuell aktualisieren, nachdem der Build verarbeitet wurde, damit du den Build an deine Nutzer verteilen kannst.
Das ist nur ein optionaler Parameter, den du aktualisieren kannst, wenn du bei privaten Projekten Build-Minuten sparen willst. Bei kostenlosen Projekten sollte das gar kein Problem sein. Siehe Preise.
3. GitHub Actions einrichten
GitHub Secrets konfigurieren
Hast du dich schon mal gefragt, woher die Werte von ENV kommen? Kein Geheimnis mehr – sie stammen aus dem Secret deines Projekts. 🤦

1. APP_STORE_CONNECT_TEAM_ID - die ID deines App Store Connect Teams, falls du in mehreren Teams bist.
2. DEVELOPER_APP_ID - gehe in App Store Connect zur App → App-Informationen → Scrolle nach unten zum General Information Bereich deiner App und suche nach Apple ID.
3. DEVELOPER_APP_IDENTIFIER - dem Bundle Identifier deiner App.
4. DEVELOPER_PORTAL_TEAM_ID - der ID deines Developer-Portal-Teams, falls du in mehreren Teams bist.
5. FASTLANE_APPLE_ID - der Apple ID oder der Entwickler-E-Mail, mit der du die App verwaltest.
6. GIT_USERNAME & GIT_TOKEN - deinem Git-Benutzernamen und deinem Personal Access Token.
7. MATCH_PASSWORD - der Passphrase, die du bei der Initialisierung von match vergeben hast; sie wird zum Entschlüsseln der Zertifikate und Provisioning Profiles verwendet.
8. PROVISIONING_PROFILE_SPECIFIER - match AppStore <YOUR_APP_BUNDLE_IDENTIFIER>, z. B. match AppStore com.domain.blabla.demo.
9. TEMP_KEYCHAIN_USER & TEMP_KEYCHAIN_PASSWORD - weise deinem Workflow einen temporären Keychain-User und ein Passwort zu.
10. APPLE_KEY_ID — App Store Connect API Key 🔺Key ID.
11. APPLE_ISSUER_ID — App Store Connect API Key 🔺Issuer ID.
12. APPLE_KEY_CONTENT — App Store Connect API Key 🔺 Key-Datei oder Key-Inhalt von .p8, Prüfen
13. CERTIFICATE_STORE_URL — Die Repo-URL deiner Match-Keys (z. B.: https://github.com/***/fastlane_match.git)
4. GitHub Workflow-Datei konfigurieren
Erstelle ein GitHub Workflow-Verzeichnis.
cd .github/workflows
Im workflow Ordner erstelle eine Datei namens build-upload-ios.ymlund füge Folgendes hinzu.
name: Build source code on ios
on:
push:
tags:
- '*'
jobs:
build_ios:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v6
- name: set Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
cache: npm
- name: Install dependencies
id: install_code
run: npm ci
- name: Build
id: build_code
run: npm run build
- uses: actions/cache@v5
with:
path: ios/App/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-
- name: Sync
id: sync_code
run: npx cap sync
- uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7.2
- uses: maierj/fastlane-action@v2.3.0
env:
DEVELOPER_APP_IDENTIFIER: ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
DEVELOPER_APP_ID: ${{ secrets.DEVELOPER_APP_ID }}
PROVISIONING_PROFILE_SPECIFIER: match AppStore ${{ secrets.DEVELOPER_APP_IDENTIFIER }}
TEMP_KEYCHAIN_USER: ${{ secrets.TEMP_KEYCHAIN_USER }}
TEMP_KEYCHAIN_PASSWORD: ${{ secrets.TEMP_KEYCHAIN_PASSWORD }}
APPLE_ISSUER_ID: ${{ secrets.APPLE_ISSUER_ID }}
APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }}
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
CERTIFICATE_STORE_URL: https://github.com/${{ secrets.CERTIFICATE_STORE_REPO }}.git
GIT_USERNAME: ${{ secrets.GIT_USERNAME }}
GIT_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
FASTLANE_APPLE_ID: ${{ secrets.FASTLANE_APPLE_ID }}
MATCH_USERNAME: ${{ secrets.FASTLANE_APPLE_ID }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APP_STORE_CONNECT_TEAM_ID: ${{ secrets.APP_STORE_CONNECT_TEAM_ID }}
DEVELOPER_PORTAL_TEAM_ID: ${{ secrets.DEVELOPER_PORTAL_TEAM_ID }}
with:
lane: closed_beta
- name: Upload release bundle
uses: actions/upload-artifact@v2
with:
name: ios-release
path: ./App.ipa
retention-days: 60
Dieser Workflow sollte nach jedem GitHub ausgelöst werden Tag, falls du das Tagging automatisieren möchtest, schau bitte in Automatischer Build und Release mit GitHub Actions zuerst.
Anschließend lädt dieser Workflow deine NodeJS-Abhängigkeiten herunter, installiert sie und baut deine JavaScript-App.
Jedes Mal, wenn du einen neuen Commit pushst, wird ein Release in TestFlight erstellt.
Deine App muss nicht Ionic nutzen, nur die Capacitor Basis ist Pflicht. Sie kann alte Cordova-Module enthalten, aber Capacitor JS-Plugins sollten bevorzugt werden.
5. Workflow triggern
Einen Commit erstellen
Erstelle einen Commit, dann solltest du den aktiven Workflow im Repository sehen.
Workflow auslösen
Neue Commits in den Branch pushen main oder development um den Workflow auszulösen.

Nach wenigen Minuten sollte der Build in deinem App Store Connect Dashboard verfügbar sein.

Deployment vom lokalen Rechner möglich?
Ja, das geht und ist absolut mühelos.
Stell dir vor, du hast ein privates Repository, die Minuten des Free-Plans sind aufgebraucht und du willst nicht für neue Releases zahlen – oder du reichst die App lieber manuell ein.
Packen wir es an
Ok, zuerst müssen wir in my_project_path/fastlane den Pfad zu einer Datei namens .env, direkt im selben Verzeichnis wie Fastfile, um das Gleiche erstellen zu können Secrets zu verwenden, die in unserem _GitHub zu finden sind, wie unten:
.env-Datei für das Deployment vom lokalen Rechner
Jetzt kannst du ins Terminal gehen und den Befehl starten: Fastlane von deinem Rechner:
fastlane closed_beta
❌ Wichtig bei der .env Datei: Da wir diese Daten lieber nicht preisgeben, müssen wir sie in unsere .gitignoreeintragen, etwa so: ❌
fastlane/*.env
Es sollte genauso funktionieren wie über GitHub Actions auf dem Remote-Server, nur eben auf unserem lokalen Rechner. 🍻

Terminal-Befehl: $ Fastlane closed_beta
Wenn du bis hierher gekommen bist: Glückwunsch! Du hast nun einen vollautomatisierten Prozess für deine iOS-Apps mit Fastlane und GitHub Actions.
Jedes Mal, wenn du einen neuen Commit pushst, wird ein Release in der Google Play Console im Beta-Channel gebaut. Ich werde diesen Blogpost basierend auf eurem Feedback verbessern. Falls ihr Fragen oder Anregungen habt, schreibt mir einfach eine E-Mail. martin@capgo.app
Auf deinem Gerät builden
Wenn du weiterhin auf deinem Gerät builden musst, musst du sie manuell zum Provisioning hinzufügen.
Verbinde dein Gerät mit deinem Mac und öffne das Geräte-Menü
Kopiere dann deinen Identifier
Und starte dann den Befehl:
fastlane register_new_device
Du wirst nach einem Gerätenamen und dem Identifier gefragt:

Falls Probleme auftreten
Wenn es Probleme gibt, weil das Dev-Gerät nicht testen kann usw., behebt das das meistens.
Es gibt einen magischen Befehl, der dich retten kann:
fastlane match nuke development
fastlane match development
Danach: Bereinige das Projekt mit Shift(⇧)+Command(⌘)+K oder über Product > Clean (evtl. auch „Clean Build Folder“ genannt)
Versuche dann, die App erneut auf deinem Gerät zu starten.
Danke
Dieser Blogpost basiert auf folgenden Artikeln:
- Continuous Delivery für iOS mit Fastlane und GitHub Actions
- Fastlane Dokumentation
- Diese GitHub Nachricht von @mrogunlana
Weiter mit dem automatischen Capacitor iOS-Build via GitHub Actions mit match
Wenn du es nutzt Automatisierter Capacitor iOS-Build mit GitHub Actions via match um CI/CD-Automatisierung zu planen, verbinde es mit Capgo CI/CD für den Produkt-Workflow in Capgo CI/CD, Capgo Native Builds optimieren für den Produkt-Workflow in Capgo Native Builds, Capgo Integrationen für den Produkt-Workflow in Capgo Integrations, CI/CD-Integration für die Implementierungsdetails in der CI/CD-Integration und GitHub Actions-Integration für die Implementierungsdetails in der GitHub Actions-Integration.