Fastlane med .NET MAUI: Automatisera iOS- och Android-releaser 2026
Så automatiserar du iOS- och Android-releaser i .NET MAUI med Fastlane: signering med match, uppladdning till TestFlight och Google Play, och en fungerande GitHub Actions-pipeline med riktig kod.
Fastlane fungerar utmärkt med .NET MAUI eftersom MAUI kompilerar till native .ipa- och .aab-artefakter som Fastlane sedan hanterar precis som för vilket iOS- eller Android-projekt som helst. Med rätt uppsättning av match, gym, pilot och supply går en release till TestFlight och Google Play Internal Testing på ett par minuter i stället för en halvtimmes klickande i Xcode och Play Console. De flesta team hoppar över certifikathanteringen och råkar sedan ut för att signeringen fallerar mitt i första CI-körningen (den delen är där Fastlane sparar mest tid).
Fastlane 2.232.x (februari 2026) fungerar med .NET MAUI genom att peka gym mot det Xcode-arkiv som dotnet publish producerar och supply mot .aab-artefakten från MAUI Android-bygget.
match lagrar iOS-certifikat och provisioning profiles i ett privat, krypterat git-repo. Det är enda hållbara sättet att hantera signering över flera maskiner och CI-agenter utan att synka nyckelringen manuellt.
App Store Connect API-nyckel (issuer ID, key ID, .p8-fil) ersätter Apple ID + lösenord för uppladdning till TestFlight och undviker 2FA-blockeringar i CI.
Google Play kräver en Service Account JSON med rollen Release Manager. supply init laddar ner nuvarande metadata så du kan versionera texter och skärmdumpar i git.
GitHub Actions kör iOS-lanes på macos-14-agenter och Android-lanes på ubuntu-latest. Dela upp workflow i två jobb för att spara Mac-minuter.
Vanliga fel: gym hittar inte scheme (lös med -p och explicit scheme:), match vägrar utan MATCH_PASSWORD, och supply kräver att första .aab laddas upp manuellt innan tracks fungerar.
Varför Fastlane med .NET MAUI när GitHub Actions redan finns?
Kortsvaret: Fastlane är abstraktionslagret som gör själva release-stegen deklarativa och portabla, medan GitHub Actions bara är motorn som kör dem. Ärligt talat har jag sett fler team än jag kan räkna som skriver 400 raders YAML med xcrun altool, xcodebuild -exportArchive och base64-kodade keystore-secrets, för att sedan behöva göra om alltihop när de byter till Azure DevOps eller vill kunna köra samma release lokalt från sin egen laptop. Fastlane löser det genom att flytta logiken in i en Fastfile som kör identiskt överallt.
Det handlar också om felmeddelanden. När xcodebuild spottar ut "No signing certificate 'iOS Distribution' found" är felmeddelandet ungefär lika användbart som en tegelsten. gym och match ger däremot diagnostik i klartext: vilket team, vilket bundle-id, vilken provisioning profile som saknas eller har gått ut, och vad du behöver köra för att laga det. För en MAUI-app där iOS-delen ändå är den svåraste biten är det värt tiden att sätta upp. Se även vår översikt över CI/CD för .NET MAUI med GitHub Actions som beskriver pipeline-strukturen på högre nivå.
Ytterligare en fördel är metadata as code. deliver och supply synkar App Store-beskrivningar, screenshots, "What's New"-texter och åldersklassning från filer i repot. Ändringar går genom pull request i stället för att någon manuellt klickar i Play Console strax innan release, vilket i sin tur eliminerar hela klassen av "vem ändrade beskrivningen"-incidenter.
Förutsättningar och installation
Fastlane är Ruby, så vi behöver en modern Ruby-runtime och Bundler. På macOS (som du behöver för iOS-bygget ändå) rekommenderar jag rbenv framför systemets Ruby. Apples inbyggda 2.6.10 fungerar, men bråkar med gemförberoenden vid varje macOS-uppgradering.
För .NET-delen behöver du .NET 10 SDK med MAUI-workload installerad (dotnet workload install maui). Kontrollera att dotnet publish -f net10.0-ios -c Release och dotnet publish -f net10.0-android -c Release producerar artefakter innan du börjar med Fastlane. Det är enklare att felsöka byggfel separat än blandat med signeringsproblem.
Projektstruktur: Appfile, Fastfile och miljövariabler
Kör bundle exec fastlane init och välj "Manual setup", eftersom auto-detection förutsätter en .xcodeproj vilket MAUI inte har. Det skapar mappen fastlane/ med två filer: Appfile (identiteter) och Fastfile (lanes). Min rekommenderade struktur för ett MAUI-projekt ser ut så här:
MyMauiApp/
├── MyMauiApp.csproj
├── Platforms/
├── Gemfile
├── Gemfile.lock
└── fastlane/
├── Appfile # Bundle-id + team-id
├── Fastfile # Alla lanes
├── Matchfile # match-konfiguration
├── Pluginfile # (om du använder plugins)
└── metadata/ # (skapas av deliver/supply)
Appfile ska hålla identiteter, inte hemligheter, och den checkas in i git:
# fastlane/Appfile
app_identifier("com.mycompany.mymauiapp")
apple_id(ENV["FASTLANE_APPLE_ID"]) # din utvecklarkonto-email
itc_team_id("123456789") # App Store Connect team ID
team_id("ABCD123456") # Developer Portal team ID
for_platform :android do
package_name("com.mycompany.mymauiapp")
json_key_file("./fastlane/google-play-key.json") # git-ignoreras
end
Hemligheter (App Store Connect API-nyckel, MATCH_PASSWORD, keystore-lösenord) ska aldrig ligga i filer. Lokalt läser Fastlane dem från en .env-fil som .gitignore:as; i CI kommer de från repository secrets.
iOS-signering med match utan att röra nyckelringen
match är den enskilt största anledningen att använda Fastlane. I stället för att varje utvecklare och CI-agent behöver egna certifikat och profiles genererar match ett gemensamt set som lagras krypterat i ett privat git-repo (typiskt your-org/certificates). Alla synkar därifrån med samma MATCH_PASSWORD.
# Första gången: initiera match-repot
bundle exec fastlane match init
# Välj "git" som storage och ange URL till ditt privata repo
Kör sedan bundle exec fastlane match appstore första gången. Match skapar då Apple Distribution-certifikat plus provisioning profile, krypterar med MATCH_PASSWORD och pushar till repot. Nästa maskin (eller CI) som kör samma kommando drar ner filerna och installerar dem i keychain utan att någon människa behöver klicka i Apple Developer Portal.
Bygg och ladda upp iOS till TestFlight med gym och pilot
Här är där MAUI-integrationen kräver en liten omväg. gym förväntar sig ett Xcode-projekt, men MAUI bygger via MSBuild. Lösningen är att köra dotnet publish först och sedan använda Fastlanes upload_to_testflight-action direkt med den .ipa som MAUI producerar. Jag råkade själv ut för den där kompromissen när jag skeppade min första MAUI-app till TestFlight, och det tog några försök innan jag förstod att gym helt enkelt inte behövs. Här är en komplett release-lane:
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Bygg och distribuera till TestFlight"
lane :beta do
# 1. Synka certifikat och profiles från match-repot
match(
type: "appstore",
readonly: true, # CI får aldrig skapa nya certifikat
app_identifier: "com.mycompany.mymauiapp"
)
# 2. Hämta nästa buildnummer från TestFlight
build_number = latest_testflight_build_number(
api_key_path: "./fastlane/app-store-key.json",
version: get_version_number_from_csproj
) + 1
# 3. Bygg iOS-appen med dotnet CLI
sh(
"dotnet publish ../MyMauiApp.csproj " "-f net10.0-ios -c Release " "/p:ArchiveOnBuild=true " "/p:ApplicationDisplayVersion=1.4.0 " "/p:ApplicationVersion=#{build_number} " "/p:CodesignProvision='match AppStore com.mycompany.mymauiapp' " "/p:CodesignKey='Apple Distribution: MyCompany AB (ABCD123456)'"
)
# 4. Ladda upp .ipa till TestFlight
upload_to_testflight(
api_key_path: "./fastlane/app-store-key.json",
ipa: "../bin/Release/net10.0-ios/ios-arm64/publish/MyMauiApp.ipa",
skip_waiting_for_build_processing: true,
changelog: "Automatisk build ##{build_number} från main"
)
end
end
def get_version_number_from_csproj
csproj = File.read("../MyMauiApp.csproj")
csproj.match(/<ApplicationDisplayVersion>(.+?)</)[1]
end
Nyckeldetaljer: CodesignProvision pekar på det profil-namn som match just installerade (match AppStore <bundle-id>), och CodesignKey måste matcha certifikatets Common Name exakt. Om upload_to_testflight misslyckas med "No suitable application record found", kontrollera att bundle-id:t verkligen är registrerat i App Store Connect först. pilot skapar inte appar automatiskt.
För distribution till specifika testergrupper, lägg till groups: ["QA-team", "Product"] och distribute_external: true. Externa grupper kräver en review-runda hos Apple första gången, men bara sekunder för efterföljande builds.
Android: keystore-hantering och Play Store-uppladdning med supply
Android-flödet är enklare eftersom det inte finns någon Apple-motsvarighet till provisioning profiles: bara en keystore och ett Google Play API-konto. Håll dock keystoren utanför repot. Förlorar du din signing key kan du inte publicera uppdateringar utan att skapa en ny app-listning. Använd Google Play App Signing (aktivera vid första uppladdning) så att Google håller distributionsnyckeln och din upload key kan roteras vid behov.
# fastlane/Fastfile - fortsättning
platform :android do
desc "Bygg AAB och ladda upp till Internal Testing"
lane :internal do
# 1. Bygg signerad AAB med dotnet CLI
sh(
"dotnet publish ../MyMauiApp.csproj " "-f net10.0-android -c Release " "/p:AndroidPackageFormat=aab " "/p:AndroidKeyStore=true " "/p:AndroidSigningKeyStore=#{ENV['ANDROID_KEYSTORE_PATH']} " "/p:AndroidSigningKeyAlias=#{ENV['ANDROID_KEY_ALIAS']} " "/p:AndroidSigningKeyPass=env:ANDROID_KEY_PASSWORD " "/p:AndroidSigningStorePass=env:ANDROID_STORE_PASSWORD"
)
# 2. Ladda upp till Google Play Internal Testing track
upload_to_play_store(
package_name: "com.mycompany.mymauiapp",
track: "internal",
aab: "../bin/Release/net10.0-android/publish/com.mycompany.mymauiapp-Signed.aab",
json_key: "./fastlane/google-play-key.json",
release_status: "completed",
skip_upload_apk: true,
skip_upload_metadata: false,
skip_upload_images: false
)
end
desc "Befordra internal till beta"
lane :promote_beta do
upload_to_play_store(
package_name: "com.mycompany.mymauiapp",
json_key: "./fastlane/google-play-key.json",
track: "internal",
track_promote_to: "beta"
)
end
end
Service account JSON-filen skapar du under Google Cloud Console → IAM → Service Accounts, bjuder in kontot i Play Console under Users and permissions och tilldelar Release manager-rollen för appen. fastlane supply init laddar sedan ner all befintlig metadata (beskrivningar, skärmdumpar, "What's New" per språk) till fastlane/metadata/android/. Hädanefter versioneras texter i git och pushas vid varje release.
Integrera Fastlane i GitHub Actions
Så, dela upp workflowet i två jobb: iOS på macos-14 (Mac-minuter är fem gånger dyrare) och Android på ubuntu-latest. Här är en fungerande .github/workflows/release.yml:
Två saker som är lätta att missa. För det första ska MATCH_GIT_BASIC_AUTHORIZATION vara echo -n "x-access-token:<GITHUB_PAT>" | base64, inte bara token, annars kan match inte klona certifikatrepot. Jag upptäckte det misstaget själv först efter tre misslyckade CI-körningar. För det andra sparar bundler-cache: true i setup-ruby ungefär 30-60 sekunder per körning genom att cacha vendor/bundle.
För push-notiser i din release-pipeline, se separat guide om APNs och FCM i .NET MAUI 2026. Det påverkar vilka entitlements match måste inkludera i provisioning profile.
Vanliga fallgropar och felsökning
Efter att ha satt upp Fastlane för ett tiotal MAUI-team ser jag samma fel gång på gång. Kolla dessa innan du gräver djupare.
gym / build_app hittar inte scheme
MAUI har inget Xcode-scheme att peka på. Använd sh("dotnet publish ...") direkt i stället för gym. Om du absolut vill använda gym för att få dess arkiv-hantering, kör dotnet publish /p:ArchiveOnBuild=true först och peka sedan gym mot .xcarchive med skip_build_archive: true.
match: "Could not find valid code signing identity"
Nio gånger av tio betyder det att keychainet är låst i CI. Lägg till i början av iOS-laneen:
setup_ci if ENV['CI']
# eller manuellt:
create_keychain(name: "ci", password: SecureRandom.uuid, unlock: true, lock_when_sleeps: false)
setup_ci är en Fastlane-action som skapar och låser upp ett temporärt keychain för CI-körningen. Utan det installerar match certifikaten i ett låst keychain och gym kan inte signera.
supply: "Package not found: com.mycompany.mymauiapp"
Första .aab:n måste laddas upp manuellt via Play Console. Google godkänner inte att första builden går via API. Efter det fungerar supply för alla efterföljande releaser. Kontrollera också att service account faktiskt har fått rollen tilldelad för specifika appen, inte bara på organisationsnivå.
upload_to_testflight fastnar i "Waiting for build processing"
Sätt skip_waiting_for_build_processing: true så returnerar pilot direkt efter uppladdning. Bearbetningen tar 5-30 minuter hos Apple och det finns ingen anledning att bränna Mac-minuter medan CI väntar. Kör en separat post-processing-lane som pollar efteråt om du vill notifiera Slack när builden är redo.
Versionsnummer krockar
Använd alltid latest_testflight_build_number + 1 från pilot som källa till buildnummer, inte GitHub Actions run_number. Om du någonsin behöver bygga om en gammal commit på en ny agent kommer run_number-approach:en att generera lägre nummer än vad TestFlight redan sett och rejecta uppladdningen.
För en djupare genomgång av hur MAUI-arkitekturen påverkar release-strategin, se vår migreringsguide från Xamarin till .NET MAUI. Flera signerings-gotchas har ändrats sedan Xamarin.iOS.
Kan man använda Fastlane med .NET MAUI eller kräver det ett native projekt?
Ja, Fastlane fungerar med .NET MAUI. Eftersom MAUI kompilerar till standard .ipa- och .aab-artefakter kan Fastlanes upload-actions (upload_to_testflight, upload_to_play_store) hantera dem direkt. Byggsteget kör du med dotnet publish i en sh-block i stället för att förlita dig på Fastlanes inbyggda gym-action.
Behöver man en Mac för att köra Fastlane med MAUI?
För iOS-releaser: ja, Xcode-toolchain och kodsignering kräver macOS. För Android-releaser räcker Linux eller Windows. Vanlig setup är att dela workflow i två jobb (iOS på macOS-agent och Android på Ubuntu) för att minimera dyra Mac-minuter i CI.
Vad är skillnaden mellan Fastlane match och att lagra certifikat lokalt?
match lagrar iOS-certifikat och provisioning profiles krypterat i ett privat git-repo som hela teamet och CI kan synka från. Lokala keychains fungerar för en enskild utvecklare men fallerar så fort du har flera maskiner: certifikat behöver återkallas och skapas om, profiles hamnar ur synk, och CI-agenter kräver manuell installation. match gör att alla får samma set utan handpåläggning.
Hur hanterar man App Store Connect 2FA i CI utan att blockera automation?
Använd App Store Connect API-nyckel i stället för Apple ID + lösenord. API-nyckeln (issuer ID + key ID + .p8-privatnyckel) autentiseras direkt mot Apples backend utan 2FA-krav. Skapa nyckeln under Users and Access → Integrations i App Store Connect och lagra .p8-filen som GitHub secret. Fastlane läser den via api_key_path-parametern.
Fungerar Fastlane snapshot för att generera skärmdumpar i .NET MAUI?
Nej, inte direkt. snapshot förutsätter XCUITest och kan inte adressera MAUI-kontroller via native accessibility-IDs utan extra arbete. För .NET MAUI genererar de flesta team skärmdumpar med Appium eller MAUI:s egna UI-test-runner (Microsoft.Maui.Controls.Xaml.UnitTests) och laddar sedan upp bilderna med deliver/supply i metadata-mappen.
Bygg en produktionsklar CI/CD-pipeline för .NET MAUI med GitHub Actions. Signera iOS och Android säkert, distribuera till TestFlight och Google Play, och kapa byggtiden till 12 till 18 minuter med caching.