CI/CD for .NET MAUI med GitHub Actions: Automatiseret Build og Deployment til iOS og Android (2026)
Komplet 2026-opskrift på .NET MAUI CI/CD med GitHub Actions: separate iOS- og Android-workflows, signering med secrets, samt deploy til TestFlight og Google Play.
CI/CD for .NET MAUI med GitHub Actions kræver to separate workflows: en macos-14-runner til iOS-builds (signering, IPA-pakkning, upload via App Store Connect API) og en ubuntu-latest-runner til Android (AAB-pakkning, signering med upload key, deploy via Google Play Developer API). Jeg har shippet til både Play Store og App Store siden 2019, og jeg deler den nøjagtige pipeline-opskrift, vi bruger på .NET 9 i 2026, inklusive secret-håndtering, code signing, og hvordan vi holder build-tiden under 20 minutter på begge platforme.
Brug separate GitHub Actions-jobs til iOS (macOS-runner) og Android (Ubuntu-runner), og kør dem parallelt for at halvere køtiden.
iOS-signering kræver et .p12-certifikat og en provisioning profile gemt som base64-encodede GitHub Secrets, ikke i repoet.
Android Bundle (.aab) er obligatorisk for nye apps på Google Play siden august 2021. APK accepteres ikke længere til Play Store-distribution.
App Store Connect API med en JWT-baseret nøgle erstatter Apple ID + 2FA og fjerner den eneste reelle blokering for fuld iOS-automatisering.
.NET 9 MAUI-workload skal installeres eksplicit i pipelinen via dotnet workload install maui. Den ligger ikke i runner-imagets standard-SDK.
Realistisk build-tid for en mellemstor MAUI-app er 8–12 minutter på Android og 14–18 minutter på iOS, dybt afhængig af om du cacher NuGet og workloads.
Hvorfor CI/CD for .NET MAUI ikke er "bare en build-fil"
Hvis du kommer fra en ren backend-baggrund, virker mobil-CI/CD som overkill. Det er det ikke. En .NET MAUI-app skal bygges som mindst tre kunstefakter (Android AAB, iOS IPA, og potentielt en Windows MSIX), og hver af dem har sit eget certifikatslager, sin egen butiks-API og sin egen sandkasse af platform-specifikke fejl. I praksis betyder det, at en velsmurt pipeline ikke bare bygger koden. Den forbereder runner-imaget, installerer .NET 9 MAUI-workloaden, injicerer signaturmateriale fra en hemmelighedstjeneste, og uploader til den rigtige butik via en autentificeret API.
Vi besluttede tidligt at flytte væk fra Azure DevOps til GitHub Actions primært af én grund: gratis macOS-runnere til offentlige repoer og en rimelig prismodel (10 USD-credit/måned for private). Det betyder også, at hele konfigurationen lever i .github/workflows/ ved siden af koden, hvilket gør PR-driven pipeline-ændringer trivielle. Vi behandlede testarbejdet separat i vores teststrategi-guide, og denne artikel bygger oven på den.
Forudsætninger og runner-valg
Før du opretter den første workflow-fil, skal du have fire ting på plads. Disse er ikke valgfri. Manglende ting her er den primære årsag til de "virker på min Mac"-fejl, vi alle har set:
Apple Developer Program-medlemskab (99 USD/år), som kræves for både signering og distribution.
Google Play Console-konto (engangsgebyr 25 USD) med en service account oprettet under Setup → API access.
En valid signeringsnøgle for Android (genereres med keytool) og et Distribution-certifikat samt provisioning profile for iOS.
Et GitHub Environment per platform (android-prod, ios-prod) med required reviewers, så ingen ved et uheld pusher til Play Store fra en feature branch.
Til runner-valg: brug macos-14 (Sonoma med Apple Silicon) for iOS. Den er hurtigere end de gamle Intel-runnere og er nu standard i GitHub Actions. For Android er ubuntu-latest tilstrækkeligt, fordi MAUI Android-builds ikke kræver Mac. Brug aldrig macOS til Android, medmindre du har et meget specifikt scenarie. macOS-minutter koster 10× det Linux koster på private repoer. På et team, der bygger 50 PR'er om dagen, er det forskellen mellem at bruge 30 USD og 300 USD om måneden alene på Android-jobs.
Android-workflow med GitHub Actions
Lad os starte med Android, fordi den er enklere. Workflowen nedenfor bygger en signeret Android App Bundle (.aab) på hver push til main, og den uploader artefakten til job-outputtet. Så har du allerede 80% af pipelinen klar. Bemærk, at vi henter både SDK og MAUI workload eksplicit; ubuntu-runneren har ikke MAUI forudinstalleret.
Et par detaljer der ofte snyder folk: AndroidPackageFormat=aab er obligatorisk siden august 2021, og Play Store tager ikke imod APK'er fra nye apps længere. Og keystore-passwords må aldrig stå i csproj-filen; de kommer ind via miljøvariabler, som MSBuild læser direkte. Hvis du ser fejlen "Keystore file does not exist", skyldes det stort set altid, at base64-decoded-trinnet løb i et andet working directory end build-trinnet. (Jeg ramte præcis den fejl første gang jeg satte pipelinen op, og brugte alt for længe på at debugge den.)
iOS-workflow: signering uden en Mac på skrivebordet
iOS er hvor 80% af nye CI/CD-implementeringer bryder sammen, og det er næsten altid signeringen. Apple kræver, at din build-runner har dit Distribution-certifikat i sin keychain og din provisioning profile på sit dataområde. Begge dele skal injiceres ved hver kørsel, fordi GitHub Actions-runnere er flygtige. Workflowen nedenfor håndterer hele dansen:
Værdierne for CodesignKey og CodesignProvision skal matche præcist det, der står i dit certifikatsnavn og profile-navn. Copy-paste fra Xcode → Build Settings, hvis du ikke er sikker. Hvis du ser fejlen "No installed provisioning profiles match", skyldes det næsten altid, at CFBundleIdentifier i Info.plist ikke matcher det App ID, profilen er udstedt til.
Sådan håndterer du certifikater og secrets sikkert
Reglen er enkel: ingen nøgler, certifikater eller passwords i Git nogensinde, ikke engang i en privat repo. Alt går gennem GitHub Secrets (eller endnu bedre, GitHub Environments med required reviewers). Sådan koder du et certifikat til at gemme det som secret:
# På din lokale Mac:
base64 -i Distribution.p12 -o cert.b64
base64 -i MyApp.mobileprovision -o profile.b64
base64 -i android-release.keystore -o keystore.b64
# Indsæt indholdet af hver .b64-fil i GitHub repo settings →
# Secrets and variables → Actions → New repository secret
For Android Play Store-upload skal du have en service account JSON-nøgle. Generer den i Google Cloud Console, tilknyt rollen "Service Account User" og inviter kontoen ind i Play Console med Release Manager-rettigheder. Gem hele JSON-blobben som secret PLAY_STORE_SERVICE_ACCOUNT_JSON. For iOS-upload skal du bruge en App Store Connect API-nøgle (.p8-fil), der erstatter Apple ID + 2FA. Uden den vil din pipeline blive blokeret, hver gang Apple beder om en SMS-kode. Vores eksisterende sikkerhedsguide til .NET MAUI dækker secret-håndtering på enheden; her handler det om secrets i pipelinen, hvor angrebsfladen er en anden. En exfiltreret service account-nøgle kan ramme alle dine brugere på én gang.
Deploy til TestFlight og Google Play Internal Testing
Builds er kun halvdelen af jobbet. Den fulde gevinst ved CI/CD kommer, når en commit til main automatisk lander i hænderne på dine beta-testere uden manuelle skridt. Til Android bruger vi det officielle upload-google-play action:
- name: Upload to Play Store Internal track
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
packageName: com.mycompany.myapp
releaseFiles: '**/bin/Release/net9.0-android/publish/*.aab'
track: internal
status: completed
Til iOS bruger vi Apples officielle altool (eller den nyere xcrun notarytool til notarisering, hvis du laver Mac Catalyst):
I praksis kombinerer vi disse steps med en GitHub Environment-protection rule, der kræver en manuel godkendelse, før upload-jobbet kører. Det giver os automatisering uden at miste den "menneskelige kontrol", før noget rammer butikkerne. Honestly, vi har set teams overspringe det og fortryde det inden for en måned. Typisk fordi en tilfældig PR-merge sendte en debug-build til 2.000 betatesterne.
Optimering: cache, matrix-builds og build-tid
Vores første pipeline tog 28 minutter for iOS og 19 minutter for Android. Det var ubrugeligt; udviklere ventede en halv time på PR-feedback. Her er de tre ændringer, der bragte os til hhv. 16 og 9 minutter:
Optimering
iOS-tid (før → efter)
Android-tid (før → efter)
NuGet pakke-cache
28 min → 22 min
19 min → 14 min
MAUI workload-cache
22 min → 18 min
14 min → 11 min
Inkrementel publish (uden full restore)
18 min → 16 min
11 min → 9 min
Den største enkelt-gevinst er at cache ~/.dotnet/workload-mappen. Workload-restore koster typisk 3–4 minutter per build, og det undgår du fuldstændigt med en cache-hit. Vores ydeevneguide til .NET MAUI diskuterer optimering af selve appen. Her handler det om at optimere selve build-processen, hvilket har en helt anden ROI: hvert minut sparet ganger sig med antallet af daglige builds på tværs af hele teamet.
Almindelige fejl og hvordan du fejlfinder dem
Efter at have hjulpet et halvt dusin teams op at køre, ser jeg de samme fem fejl igen og igen. Her er det korte opslagsværk:
"workload manifest not found": du har glemt at køre dotnet workload install maui-android (eller maui-ios) før dotnet publish. Tilføj det som dedikeret step.
"No valid iOS code signing keys": keychainen er låst, fordi set-key-partition-list ikke blev kørt. Det step er nemmest at glemme.
"Cannot find provisioning profile": App ID i Info.plist matcher ikke profilen. Tjek både Bundle ID, og at profilen er Distribution, ikke Development.
"Upload failed: app version already exists": Play Store og TestFlight afviser duplicate versioner. Bump ApplicationDisplayVersion og ApplicationVersion i csproj via p:-flag, gerne baseret på github.run_number.
Pipelinen står stille i 6 timer: næsten altid en interaktiv prompt. Tjek dine security-kommandoer; de spørger om password, hvis miljøvariablen ikke er sat. Sæt CI=true hvor du kan.
Ofte stillede spørgsmål
Kan jeg bygge .NET MAUI iOS-apps med GitHub Actions uden en egen Mac?
Ja. GitHub Actions har macOS-runnere (macos-14 med Apple Silicon i 2026), og de inkluderer Xcode forudinstalleret. Du behøver kun et Apple Developer-medlemskab og en App Store Connect API-nøgle. Selve build-maskinen er fuldt sky-baseret. Bemærk, at macOS-minutter koster 10× det, Linux-minutter koster på private repoer.
Hvor lang tid tager en typisk .NET MAUI-build i CI/CD?
For en mellemstor app (50–100 ViewModels, 20–30 sider) ser vi 9–12 minutter på Android med caching og 14–18 minutter på iOS. Uden cache fordobles begge nemt. Den største enkeltfaktor er, om MAUI-workloaden caches mellem kørsler. Det sparer typisk 3–4 minutter alene.
Skal jeg bruge App Bundle (.aab) eller APK til Play Store?
App Bundle. Google Play kræver .aab for alle nye apps siden august 2021, og eksisterende apps skal også bruge AAB ved store opdateringer. APK er stadig brugbar til sideloading og alternative butikker (Amazon, Samsung Galaxy Store), men ikke til Play Store-distribution.
Hvordan håndterer jeg version-numre i .NET MAUI CI/CD?
Brug github.run_number som build-nummer og send det ind som MSBuild-property: -p:ApplicationVersion=${{ github.run_number }}. Til selve visningsversionen kan du bruge en semver fra en git-tag eller en manuel input på workflow_dispatch. Det vigtige er, at både Play Store og TestFlight afviser duplicate versioner.
Er Azure DevOps eller GitHub Actions bedst til .NET MAUI?
Begge fungerer godt, men GitHub Actions har vundet på pris og udvikleroplevelse de sidste to år. Azure DevOps har en lidt mere udviklet macOS-runner (Hosted macOS) for store organisationer, men for de fleste teams er GitHub Actions enklere, billigere og bedre integreret med din kode. Vi flyttede vores team i 2024 og har ikke fortrudt det.
Cross-platform engineering lead who's shipped apps to millions on both Play Store and App Store. Believes shared codebases shouldn't mean shared mediocrity.
Sådan opsætter du type-safe REST API-integration i .NET MAUI med IHttpClientFactory, Refit og Polly. Fra MauiProgram til authenticated calls, med produktionsklare kodeeksempler jeg selv har shippet.
Lær at bygge en hurtig, sikker og vedligeholdbar lokal database i .NET MAUI med enten sqlite-net-pcl eller Entity Framework Core 9. Inkluderer setup, repository-mønster, migrationer, SQLCipher-kryptering og performance-tips med fungerende C#-eksempler.