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 .NET MAUI Guide 2026

Uppdaterad: 4 augusti 2026

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.

# macOS: installera rbenv och Ruby 3.3
brew install rbenv ruby-build
rbenv install 3.3.6
rbenv global 3.3.6

# Verifiera
ruby -v   # ruby 3.3.6
gem install bundler

Lägg sedan Fastlane i din MAUI-projektrot via Bundler så att alla utvecklare och CI-agenter får exakt samma version:

# I MAUI-projektrot (samma nivå som .csproj)
bundle init
cat >> Gemfile <<'EOF'
gem "fastlane", "~> 2.232"
EOF

bundle install
bundle exec fastlane --version   # fastlane 2.232.2

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

Din Matchfile ser då ut så här:

# fastlane/Matchfile
git_url("[email protected]:mycompany/ios-certificates.git")
storage_mode("git")
type("appstore")            # default för release-lane
app_identifier(["com.mycompany.mymauiapp"])
username(ENV["FASTLANE_APPLE_ID"])

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:

name: Release

on:
  push:
    tags: ['v*']

jobs:
  ios:
    runs-on: macos-14
    timeout-minutes: 45
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Installera MAUI workload
        run: dotnet workload install maui-ios

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.3'
          bundler-cache: true

      - name: Skapa App Store Connect API-nyckel
        env:
          APP_STORE_KEY_JSON: ${{ secrets.APP_STORE_KEY_JSON }}
        run: echo "$APP_STORE_KEY_JSON" > fastlane/app-store-key.json

      - name: Kör beta-lane
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_TOKEN }}
          FASTLANE_APPLE_ID: ${{ secrets.APPLE_ID }}
        run: bundle exec fastlane ios beta

  android:
    runs-on: ubuntu-latest
    timeout-minutes: 25
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: '10.0.x'

      - name: Installera MAUI Android workload
        run: dotnet workload install maui-android

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.3'
          bundler-cache: true

      - name: Avkoda keystore
        env:
          KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
        run: echo "$KEYSTORE_B64" | base64 -d > upload.keystore

      - name: Skapa Play Console service account
        env:
          PLAY_KEY_JSON: ${{ secrets.PLAY_KEY_JSON }}
        run: echo "$PLAY_KEY_JSON" > fastlane/google-play-key.json

      - name: Kör internal-lane
        env:
          ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/upload.keystore
          ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
          ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
          ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
        run: bundle exec fastlane android internal

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.

Officiell dokumentation värd att bokmärka: Fastlane Actions-referens, Fastlane release notes på GitHub, och Microsofts manual provisioning-guide för .NET MAUI iOS.

Vanliga frågor

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.

Sofia Rodriguez
Om Författaren Sofia Rodriguez

Mobile DevOps engineer focused on the unglamorous stuff: build pipelines, signing, store releases, and the tooling that keeps teams shipping.