CI/CD pour .NET MAUI 10 avec GitHub Actions : Build, Signature et Déploiement Automatisés (2026)
Pipeline CI/CD complet pour .NET MAUI 10 sur GitHub Actions : YAML prêt à l'emploi pour builds Android/iOS, signature keystore, fastlane match, déploiement TestFlight et Google Play avec cache et parallélisation.
Configurer un pipeline CI/CD pour .NET MAUI 10 avec GitHub Actions consiste à définir un workflow YAML qui restaure les charges de travail maui-android et maui-ios, construit les paquets .aab et .ipa sur des runners Ubuntu et macOS, les signe avec des secrets injectés, puis les téléverse vers Google Play Console et TestFlight via fastlane. Depuis la sortie de .NET 10 en novembre 2025, la matrice de builds est enfin stable et exploitable en production sans hacks manuels. Honnêtement, ça change tout. Voici le pipeline que nous utilisons pour livrer nos applications à plusieurs millions d'utilisateurs.
.NET MAUI 10 (novembre 2025) est la première version LTS où la matrice CI/CD Android/iOS est stable sans workarounds.
Les builds Android tournent sur ubuntu-latest (rapide, économique), les builds iOS exigent macos-15 avec Xcode 16.2.
La signature Android passe par un keystore encodé en base64 injecté depuis les secrets GitHub, jamais commité.
Pour iOS, fastlane match synchronise certificats et profils via un dépôt Git chiffré, évitant les erreurs de provisioning en équipe.
Un cache correct des workloads .NET et des paquets NuGet réduit un build MAUI complet de 18 minutes à environ 6 minutes.
Le déploiement multi-piste (internal, alpha, production) évite les crashs en masse et respecte les règles de mise à jour progressive de Google Play.
Prérequis et structure du dépôt
Avant d'écrire une seule ligne de YAML, on prend le temps de fixer l'arborescence du projet. Sur les équipes que je pilote, on impose une convention simple : un seul projet .csproj multi-target à la racine de src/, un dossier build/ qui contient les scripts fastlane et les fichiers de configuration de signature, et un dossier .github/workflows/ avec des workflows séparés pour Android, iOS, et la publication. Cette séparation évite les workflows monolithiques de 500 lignes qui deviennent illisibles au bout de trois mois.
Côté SDK, on cible net10.0-android et net10.0-ios. Le fichier global.json doit épingler la version exacte du SDK .NET (par exemple 10.0.100) pour éviter que le runner GitHub ne pioche une version différente selon la disponibilité :
Enfin, on active la restauration déterministe des paquets NuGet en ajoutant <RestoreLockedMode>true</RestoreLockedMode> et un packages.lock.json commité. Sans ça, le build reproductible entre local et CI reste une illusion, et c'est précisément le genre de dérive qui casse une release un vendredi soir (j'en ai vécu deux, plus jamais).
Workflow GitHub Actions pour Android
Le workflow Android tourne sur ubuntu-latest. C'est le runner le plus rapide et le moins cher en minutes GitHub (2× moins cher que macOS). Voici le fichier complet .github/workflows/android-release.yml que nous déployons en production :
Deux points méritent attention. D'abord, --skip-manifest-update sur workload install évite un aller-retour réseau qui ajoute 45 secondes à chaque build. Ensuite, on cible AndroidPackageFormat=aab plutôt que apk parce que Google Play refuse les APK depuis août 2021 pour les nouvelles applications, et l'App Bundle réduit la taille téléchargée de 20 à 40 % pour l'utilisateur final. Un détail, mais un vrai.
Signature Android avec un keystore versionné
La signature est l'endroit où la plupart des équipes se prennent les pieds dans le tapis. Le piège classique : commiter le .keystore dans le dépôt "parce que c'est plus simple". Ne faites jamais ça. Un keystore volé signifie qu'un attaquant peut publier une mise à jour malveillante de votre application avec la signature légitime, et Google ne détectera rien.
Voici notre approche. Le keystore vit uniquement sur un poste sécurisé (idéalement, un HSM ou un coffre-fort d'équipe type 1Password Vault). Pour la CI, on l'encode en base64 et on le stocke comme secret GitHub :
base64 -i release.keystore -o release.keystore.b64
# Copier le contenu dans le secret ANDROID_KEYSTORE_BASE64
Ensuite, dans le workflow, on décode le secret vers un fichier temporaire juste avant le build :
Le build iOS impose un runner macOS. C'est une contrainte d'Apple, pas de GitHub. On utilise macos-15 avec Xcode 16.2 (compatible avec le SDK iOS 18.2 requis par .NET 10). Le runner macOS coûte 10× plus cher en minutes que Ubuntu, donc chaque optimisation compte.
Le paramètre ArchiveOnBuild=true est crucial. Sans lui, dotnet publish produit un .app non signé qui refuse le téléversement vers App Store Connect (je me suis fait avoir sur ce point pendant deux heures la première fois). Consultez la documentation officielle Microsoft sur le déploiement iOS .NET MAUI pour la liste complète des propriétés MSBuild acceptées.
Gestion des certificats iOS avec fastlane match
C'est le point douloureux historique du développement iOS en équipe. Chaque développeur télécharge ses certificats depuis le portail Apple, les partage par email (parfois...), et personne ne sait plus lequel est valide. fastlane match résout ça en stockant les certificats et profils dans un dépôt Git privé, chiffrés avec un mot de passe partagé.
La configuration minimale ressemble à ça. Dans build/fastlane/Matchfile :
Et dans build/fastlane/Fastfile, on définit une lane réutilisable :
default_platform(:ios)
platform :ios do
desc "Sync signing certs and provisioning profiles"
lane :sync_signing do
setup_ci if ENV['CI']
match(
type: "appstore",
readonly: true,
keychain_name: "signing_temp",
keychain_password: ""
)
end
end
setup_ci crée un trousseau temporaire dédié au job, ce qui est essentiel. Sinon, le trousseau du runner peut retenir des certificats entre jobs et provoquer des collisions imprévisibles. Le flag readonly: true garantit que la CI ne peut pas régénérer des certificats et invalider ceux des développeurs par accident, une erreur classique qui casse toute une équipe pour la journée.
Déploiement automatisé vers les stores
Une fois les artefacts signés, on les envoie aux stores. Pour Android, on utilise l'action officielle r0adkll/upload-google-play qui parle à l'API Google Play Developer. Pour iOS, on passe par fastlane pilot vers TestFlight.
Voici le job de déploiement Android, chaîné après le build :
On envoie systématiquement sur la piste internal d'abord. C'est disponible en 2-3 minutes pour les testeurs internes. Après validation manuelle ou automatisée, un workflow séparé promeut la release vers alpha, puis production avec un déploiement progressif (userFraction: 0.1 puis 0.5 puis 1.0 sur plusieurs jours).
Pour iOS, la lane fastlane :
lane :deploy_testflight do
pilot(
ipa: "../artifacts/ios/MyApp.ipa",
skip_waiting_for_build_processing: true,
api_key_path: "fastlane/AuthKey.json",
changelog: File.read("metadata/ios/changelog.txt")
)
end
skip_waiting_for_build_processing: true économise 5 à 15 minutes par run. Le traitement Apple continue en arrière-plan et notifie par email quand la build est disponible. Pour les stratégies de test qui alimentent cette pipeline, jetez un œil à notre guide complet des tests dans .NET MAUI 10.
Optimiser le temps de build : cache et parallélisation
Un pipeline MAUI naïf tourne autour de 18 minutes. Avec les optimisations qui suivent, on descend régulièrement à 6-7 minutes, ce qui change complètement la boucle de rétroaction pour l'équipe.
Cache des workloads .NET et NuGet
Les workloads maui-android et maui-ios font 1,2 Go à télécharger. Le cache GitHub Actions les persiste entre runs :
Android et iOS n'ont aucune dépendance croisée au build. On les lance en parallèle via des jobs distincts, ce qui divise le temps mur par deux (mais consomme le double de minutes CI). Sur un projet en release hebdomadaire, ça vaut largement le coût.
Utiliser NativeAOT quand pertinent
.NET MAUI 10 propose la compilation NativeAOT pour iOS, ce qui améliore le temps de démarrage à froid de 30 à 50 %. Le trade-off : les builds NativeAOT prennent 40 à 60 % plus longtemps. On les active uniquement pour les tags de release, pas sur chaque PR. Pour les détails, notre guide sur les performances .NET MAUI 10 détaille l'arbitrage.
Gestion sécurisée des secrets et de la conformité
Un pipeline CI/CD mobile manipule des secrets à haute valeur : keystores, certificats de distribution, clés d'API Play Console. Une fuite peut coûter cher. Un incident chez un éditeur mid-market en 2024 a mené à un rejet de l'app pendant 11 jours, le temps de faire tourner tous les certificats. Pas glorieux.
Trois pratiques que nous imposons :
Environments GitHub avec protection : les secrets de production vivent dans un environment production qui exige une approbation manuelle avant chaque déploiement. Un développeur ne peut pas déployer accidentellement en poussant un tag.
Rotation trimestrielle : mot de passe du keystore, tokens Google Play, mot de passe fastlane match. On planifie ça dans le calendrier d'équipe, sinon ça ne se fait jamais.
Audit trail : chaque déploiement production génère un tag Git signé et un commit dans un dépôt d'audit interne (numéro de build, checksum de l'artefact, identité du déclencheur). Utile pour les audits de conformité et pour retracer la source d'un bug en production.
Pour les workflows sensibles, on utilise l'action actions/attest-build-provenance qui produit une attestation SLSA vérifiable, devenue un pré-requis pour plusieurs clients enterprise que nous servons en 2026.
Questions fréquentes
Peut-on construire une application iOS .NET MAUI sans machine Mac ?
Non, la compilation d'un IPA .NET MAUI iOS exige toujours macOS avec Xcode installé, car Apple ne fournit pas de toolchain Linux. Les runners GitHub macos-15 ou un service comme MacStadium sont les seules options légitimes en 2026. Le mode Hot Restart de Visual Studio ne dispense pas de cette exigence pour les builds de release signés.
Combien coûte un pipeline CI/CD .NET MAUI sur GitHub Actions ?
Pour un projet privé, comptez environ 10 minutes Ubuntu (gratuites jusqu'à 2 000/mois) et 15 minutes macOS par release (à 0,08 $/minute au-delà de 500 minutes/mois). Un rythme de 4 releases par mois coûte moins de 30 $ tout compris. Les dépôts publics ont un quota macOS gratuit beaucoup plus généreux.
Faut-il utiliser Azure DevOps plutôt que GitHub Actions pour .NET MAUI ?
GitHub Actions est aujourd'hui le choix par défaut pour la plupart des équipes : intégration native avec le dépôt, écosystème d'actions plus riche, tarification transparente. Azure DevOps reste pertinent pour les entreprises qui l'ont déjà standardisé, avec des runners self-hosted macOS et une intégration TFS/Azure Boards profonde. Techniquement, les deux savent tout faire.
Comment gérer les versions incrémentales du numéro de build ?
On injecte ApplicationDisplayVersion et ApplicationVersion via des paramètres MSBuild : -p:ApplicationVersion=${{ github.run_number }}. Pour Android, ApplicationVersion devient le versionCode; pour iOS, le CFBundleVersion. Utiliser github.run_number garantit une monotonie stricte sans intervention humaine.
Comment déployer une hotfix urgente sans casser le pipeline release principal ?
On maintient une branche release/x.y par version majeure en production. Un hotfix se branche depuis release/x.y, produit un tag vx.y.z+1, et déclenche le même workflow. Le pipeline principal sur main continue à préparer la prochaine version en parallèle, sans conflit ni rollback nécessaire.
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.