Flutter CI/CD: Automatisierte Build-, Test- und Release-Pipeline
# Flutter CI/CD: Automatisierte Build-, Test- und Release-Pipeline
Eine Flutter-App manuell zu veröffentlichen ist fehleranfällig, langsam und stressig. Eine gut durchdachte CI/CD-Pipeline beseitigt den menschlichen Engpass im Release-Prozess und lässt Sie sich auf das Schreiben von Code konzentrieren, statt Builds zu beaufsichtigen. In diesem Artikel gehe ich die gesamte Pipeline durch, die ich in der Produktion einsetze — vom Linting bis zur Store-Veröffentlichung — mit echten Konfigurationsbeispielen, die Sie sofort übernehmen können.
Warum CI/CD für Flutter wichtig ist
Flutter adressiert mehrere Plattformen aus einer einzigen Codebasis. Das bedeutet, dass jedes Release potenziell ein Android-APK/AAB, ein iOS-IPA, einen Web-Build und Desktop-Artefakte umfasst. Das manuell zu erledigen ist nicht nur mühsam — es ist ein Zuverlässigkeitsrisiko. Eine CI/CD-Pipeline bietet Ihnen:
Grundlegende Pipeline-Stufen
Eine solide Flutter-CI/CD-Pipeline besteht aus vier Stufen:
Jede Stufe fungiert als Tor. Wenn ein Schritt fehlschlägt, stoppt die Pipeline und das Team wird sofort benachrichtigt.
GitHub Actions Workflow
GitHub Actions ist mein bevorzugtes Werkzeug für Flutter-CI/CD. Hier ist ein produktionsreifer Workflow, der Analyse, Tests und Builds für beide Plattformen abdeckt:
name: Flutter CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
FLUTTER_VERSION: "3.24.0"
JAVA_VERSION: "17"
jobs:
analyze-and-test:
name: Analyse & Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Abhängigkeiten installieren
run: flutter pub get
- name: Formatierung prüfen
run: dart format --set-exit-if-changed .
- name: Analyse ausführen
run: flutter analyze --fatal-infos
- name: Tests mit Coverage ausführen
run: flutter test --coverage
- name: Coverage hochladen
uses: codecov/codecov-action@v4
with:
file: coverage/lcov.info
build-android:
name: Android Build
needs: analyze-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: ${{ env.JAVA_VERSION }}
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Keystore dekodieren
env:
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
run: echo "$KEYSTORE_BASE64" | base64 --decode > android/app/release.keystore
- name: key.properties erstellen
env:
KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
run: |
cat > android/key.properties <<EOF
storePassword=$STORE_PASSWORD
keyPassword=$KEY_PASSWORD
keyAlias=$KEY_ALIAS
storeFile=release.keystore
EOF
- name: App Bundle erstellen
run: flutter build appbundle --release
- name: Artefakt hochladen
uses: actions/upload-artifact@v4
with:
name: android-release
path: build/app/outputs/bundle/release/*.aab
build-ios:
name: iOS Build
needs: analyze-and-test
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: CocoaPods installieren
run: cd ios && pod install
- name: Signierzertifikat importieren
env:
P12_BASE64: ${{ secrets.IOS_P12_BASE64 }}
P12_PASSWORD: ${{ secrets.IOS_P12_PASSWORD }}
run: |
echo "$P12_BASE64" | base64 --decode > certificate.p12
security create-keychain -p "" build.keychain
security import certificate.p12 -k build.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
security list-keychains -d user -s build.keychain
- name: Provisioning Profile installieren
env:
PROFILE_BASE64: ${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}
run: |
echo "$PROFILE_BASE64" | base64 --decode > profile.mobileprovision
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
- name: IPA erstellen
run: flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
- name: Artefakt hochladen
uses: actions/upload-artifact@v4
with:
name: ios-release
path: build/ios/ipa/*.ipaIn meinen Release-Pipelines pinne ich die Flutter-Version immer explizit fest. Sich auf "latest" zu verlassen, ist der sicherste Weg zu Überraschungsfehlern am Montagmorgen.
Codemagic-Konfiguration
Codemagic ist ein Flutter-spezialisierter CI/CD-Dienst, der Code-Signierung und Store-Veröffentlichung mit minimalem Aufwand ermöglicht. Hier ist eine `codemagic.yaml`-Konfiguration:
workflows:
production-release:
name: Production Release
max_build_duration: 60
instance_type: mac_mini_m2
environment:
flutter: 3.24.0
xcode: latest
groups:
- google_play_credentials
- app_store_credentials
- code_signing_ios
vars:
APP_ID: com.example.myflutterapp
triggering:
events:
- tag
tag_patterns:
- pattern: "v*"
include: true
scripts:
- name: Abhängigkeiten installieren
script: flutter pub get
- name: Analyse ausführen
script: flutter analyze --fatal-infos
- name: Tests ausführen
script: flutter test
- name: iOS-Code-Signierung einrichten
script: |
keychain initialize
app-store-connect fetch-signing-files "$APP_ID" \
--type IOS_APP_STORE \
--create
keychain add-certificates
- name: iOS bauen
script: |
flutter build ipa --release \
--build-number=$PROJECT_BUILD_NUMBER \
--export-options-plist=/Users/builder/export_options.plist
- name: Android bauen
script: |
flutter build appbundle --release \
--build-number=$PROJECT_BUILD_NUMBER
artifacts:
- build/ios/ipa/*.ipa
- build/app/outputs/bundle/release/*.aab
publishing:
app_store_connect:
auth: integration
submit_to_testflight: true
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: internalDas Tag-basierte Triggering kann ich nur empfehlen. Das Pushen eines `v1.2.0`-Tags sollte der einzige manuelle Schritt in Ihrem Release-Prozess sein.
Fastlane-Integration
Fastlane übernimmt die "letzte Meile" der CI/CD — Code-Signierung, Screenshots, Metadaten und Store-Uploads. Selbst wenn Sie GitHub Actions oder Codemagic zum Bauen verwenden, ist Fastlane für die Verwaltung von Store-Einreichungen unverzichtbar.
Android Fastfile
default_platform(:android)
platform :android do
desc "Auf Google Play internen Track deployen"
lane :internal do
upload_to_play_store(
track: "internal",
aab: "../build/app/outputs/bundle/release/app-release.aab",
json_key_data: ENV["GOOGLE_PLAY_JSON_KEY"],
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
desc "Von intern nach Produktion befördern"
lane :promote_to_production do
upload_to_play_store(
track: "internal",
track_promote_to: "production",
json_key_data: ENV["GOOGLE_PLAY_JSON_KEY"],
skip_upload_changelogs: false
)
end
endiOS Fastfile
default_platform(:ios)
platform :ios do
desc "Neues Release an TestFlight senden"
lane :beta do
setup_ci if ENV["CI"]
match(
type: "appstore",
app_identifier: "com.example.myflutterapp",
readonly: is_ci
)
build_app(
workspace: "Runner.xcworkspace",
scheme: "Runner",
export_method: "app-store"
)
upload_to_testflight(
skip_waiting_for_build_processing: true
)
end
desc "An den App Store senden"
lane :release do
deliver(
submit_for_review: true,
automatic_release: false,
force: true,
skip_metadata: false,
skip_screenshots: true
)
end
endIn meinen Release-Pipelines verwende ich für die iOS-Code-Signierung immer `match`, weil es Zertifikate in einem privaten Git-Repository speichert, auf das das gesamte Team konsistent zugreifen kann.
Code-Signierung für iOS und Android
Die Code-Signierung ist der Punkt, an dem die meisten CI/CD-Setups scheitern. Von Anfang an richtig einzurichten spart Wochen an Debugging.
iOS-Code-Signierung
Es gibt zwei wesentliche Ansätze:
Manueller Ansatz: Exportieren Sie Ihr `.p12`-Zertifikat und Provisioning Profile, kodieren Sie sie Base64, speichern Sie sie als CI-Secrets und dekodieren Sie sie während des Builds. Genau das zeigt der GitHub-Actions-Workflow oben.
Fastlane Match: Match speichert Ihre Zertifikate und Profile in einem verschlüsselten Git-Repository. Jedes Teammitglied und jeder CI-Runner bezieht sie aus derselben Quelle. Keine "funktioniert auf meinem Rechner"-Signierungsprobleme mehr.
# Match zum ersten Mal initialisieren
fastlane match init
# Neue Zertifikate generieren
fastlane match appstore
fastlane match developmentWichtige Punkte:
Android-Code-Signierung
Die Android-Signierung ist einfacher, erfordert aber dennoch Sorgfalt:
"code-comment">// android/app/build.gradle
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}In meinen Release-Pipelines speichere ich immer eine Sicherungskopie des Android-Keystores an einem separaten, verschlüsselten Ort. Der Verlust eines Keystores ist endgültig — Google bietet keine Möglichkeit zur Wiederherstellung.
Release-Strategie
Code auszuliefern ist nur die halbe Miete. Sie brauchen eine Strategie, die Ihnen Kontrolle, Transparenz und einen Rollback-Plan bietet.
Semantische Versionierung
Führen Sie semantische Versionierung (`MAJOR.MINOR.PATCH`) ein und machen Sie sie zum Bestandteil Ihres Workflows:
In Flutter befindet sich die Versionsinformation in der `pubspec.yaml`:
version: 2.4.1+42
# ^ Semver ^ Build-NummerDie Build-Nummer nach dem `+` muss mit jeder Store-Einreichung erhöht werden. In der CI automatisiere ich das über die Pipeline-Laufnummer:
flutter build appbundle --build-number=$GITHUB_RUN_NUMBERAutomatisierte Changelogs
Generieren Sie Changelogs aus Ihren Commit-Nachrichten. Das erfordert Disziplin bei Ihren Commit-Konventionen:
# conventional-changelog installieren
npm install -g conventional-changelog-cli
# CHANGELOG.md generieren
conventional-changelog -p angular -i CHANGELOG.md -sWenn Ihr Team Conventional Commits (`feat:`, `fix:`, `chore:`) verwendet, wird dieser Prozess vollständig automatisch.
Beta-Distribution
Veröffentlichen Sie niemals direkt in die Produktion. Nutzen Sie einen gestuften Rollout:
Für iOS dient TestFlight als Beta-Kanal. Für Android nutzen Sie die internen und geschlossenen Testkanäle von Google Play. Firebase App Distribution ist eine hervorragende Alternative für beide Plattformen, wenn Sie schnellere Feedback-Schleifen ohne Store-Review-Verzögerungen wünschen.
Rollback-Plan
Jedes Release sollte einen Notausgang haben. In der Praxis bedeutet das:
Häufige CI/CD-Fehler
Diese Fehler habe ich immer wieder gesehen — in meinen eigenen Projekten und in Teams, mit denen ich zusammengearbeitet habe.
1. Abhängigkeiten nicht cachen
Flutter lädt Dart-Pakete, Gradle-Abhängigkeiten, CocoaPods und manchmal sogar das Flutter SDK selbst herunter. Ohne Caching verschwendet jeder Build 5-15 Minuten für Downloads.
# GitHub Actions Caching-Beispiel
- uses: subosito/flutter-action@v2
with:
flutter-version: "3.24.0"
cache: true # Cached das Flutter SDK
- uses: actions/cache@v4
with:
path: |
~/.pub-cache
~/.gradle/caches
ios/Pods
key: ${{ runner.os }}-deps-${{ hashFiles('**/pubspec.lock', '**/Podfile.lock') }}2. iOS-Builds auf Linux-Runnern ausführen
iOS-Builds erfordern macOS. Das klingt offensichtlich, aber ich habe Pipelines gesehen, die `flutter build ipa` auf einem Ubuntu-Runner ausführen und dann stillschweigend scheitern. Verwenden Sie für iOS-Jobs immer `runs-on: macos-latest`.
3. Secrets im Repository hartcodieren
Keystores, API-Schlüssel, Service-Account-JSON-Dateien — nichts davon gehört in Ihr Git-Repository, nicht einmal in ein durch `.gitignore` ausgeschlossenes Verzeichnis. Nutzen Sie die verschlüsselte Secrets-Funktion Ihres CI-Anbieters oder einen dedizierten Secrets-Manager wie HashiCorp Vault.
4. Tests überspringen, um die Pipeline zu "beschleunigen"
Tests zu deaktivieren, weil sie "zu lange dauern", verfehlt den Zweck der CI. Wenn Ihre Tests langsam sind, optimieren Sie sie — verwenden Sie Mocks, führen Sie Tests parallel aus, lagern Sie Integrationstests in eine separate Stufe aus. Überspringen Sie sie nicht.
5. Keine Build-Nummer-Strategie
Wenn Ihre Build-Nummer nicht konsistent inkrementiert, werden Store-Uploads abgelehnt. Leiten Sie die Build-Nummer von etwas ab, das garantiert ansteigt: die CI-Laufnummer, ein Zeitstempel oder ein Zähler in Ihrer Versionskontrolle.
6. Instabile Tests ignorieren
Ein instabiler Test, der ignoriert wird, ist schlimmer als gar kein Test. Er gewöhnt das Team daran, CI-Fehler abzutun. Beheben oder entfernen Sie instabile Tests sofort.
7. Build- und Deploy-Stufen nicht trennen
Build und Deploy sollten getrennte Pipeline-Stufen sein. Wenn Ihr Deploy-Schritt fehlschlägt, sollten Sie dasselbe Artefakt ohne erneuten Build deployen können. Laden Sie Ihr Artefakt im Build-Schritt hoch und im Deploy-Schritt herunter.
Alles zusammenführen
Eine ausgereifte Flutter-CI/CD-Pipeline sieht so aus:
Die einzigen manuellen Schritte sind das Mergen des PRs, das Erstellen des Tags und die Freigabe der Promotion. Alles andere ist automatisiert.
Fazit
CI/CD ist für Flutter-Projekte kein Nice-to-have — es ist unverzichtbar. Die anfängliche Investition in die Pipeline-Konfiguration zahlt sich innerhalb der ersten Releases aus. Sie liefern schneller, verursachen weniger Fehler und schlafen ruhiger, weil Sie wissen, dass jedes Release denselben rigorosen, automatisierten Prozess durchlaufen hat.
Ich helfe Ihnen gern bei der Entwicklung einer CI/CD-Pipeline, die auf Ihr Flutter-Projekt und Ihren Team-Workflow zugeschnitten ist.
Verwandte Artikel
Flutter Performance-Optimierung: Vollständiger Leitfaden
Steigern Sie die Performance Ihrer Flutter-App systematisch. Lernen Sie Rebuild-Optimierung, Speichermanagement, Lazy Loading und Profiling.
Flutter Testing: Unit-, Widget- und Integrationstests
Erstellen Sie eine praktikable Flutter-Teststrategie mit klaren Rollen für Unit-, Widget- und Integrationstests.
Flutter Firebase Integration: Auth, Firestore und Push
Richten Sie Firebase in Flutter ein: Auth-Flows, Firestore-Nutzung und grundlegende Sicherheit für Produktion.
Haben Sie ein Flutter-Projekt?
Ich entwickle hochleistungsfähige Flutter-Anwendungen für iOS, Android und Web.
Kontakt aufnehmen