Chapitre 16
16 - DevOps Mobile
16 - DevOps Mobile
16 - DevOps Mobile : Cours complet
Niveau : Intermédiaire → Avancé Durée estimée : 5 h Objectif : industrialiser le cycle de vie d'une app mobile : CI/CD, Fastlane, signing, distribution, observabilité et releases automatisées.
Table des matières
- La chaîne DevOps mobile
- CI/CD
- Fastlane
- Build configuration
- Signing
- Versioning
- Distribution
- Observabilité
- Releases automatisées
1. La chaîne DevOps mobile
1.1 De commit à production
Diagramme en cours de génération...
1.2 Les 3 environnements
| Environnement | Build | Distribué à | Signé avec |
|---|---|---|---|
| Debug | dev | Développeurs | Clé debug |
| Staging/TestFlight | test | QA, stakeholders | Certif dev/ad hoc |
| Production | release | Public | Certif distribution |
1.3 Les objectifs
- Répétabilité : le même commit produit le même build.
- Traçabilité : on sait quel commit a produit quel build.
- Vitesse : feedback < 10 min sur une PR.
- Fiabilité : zéro build manuel avant mise en prod.
2. CI/CD
2.1 Les plateformes
| Plateforme | Points forts | Cas d'usage |
|---|---|---|
| GitHub Actions | Gratuit (open source), écosystème, matrices | Standard 2026 |
| Bitrise | Orientée mobile, steps prêtes | Équipes mobiles pures |
| GitLab CI | Intégré à GitLab, runners auto | Tout GitLab |
| Codemagic | Flutter-first, iOS sans Mac | Flutter |
| CircleCI | Rapide, orchestration fine | iOS/Android avancés |
2.2 GitHub Actions — workflow mobile
name: Android CI
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: 17 }
- uses: gradle/actions/setup-gradle@v4
- name: Lint + tests
run: ./gradlew lint testDebugUnitTest
android-build:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build release
run: ./gradlew assembleRelease
- name: Upload AAB
uses: actions/upload-artifact@v4
with: { name: aab, path: app/build/outputs/bundle/release/*.aab }
2.3 iOS sur macOS
ios-build:
runs-on: macos-15
needs: test
steps:
- uses: actions/checkout@v4
- name: Build & test
run: xcodebuild test -scheme TaskFlow -destination 'platform=iOS Simulator,name=iPhone 16'
- name: Archive
run: xcodebuild -scheme TaskFlow archive -archivePath build/TaskFlow.xcarchive
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath build/TaskFlow.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath build/ipa
2.4 Cache et durée de vie
| Plateforme | Cache |
|---|---|
| Android | ~/.gradle, ~/.konan |
| iOS | DerivedData, SPM cache |
| Flutter | ~/.pub-cache |
| RN | node_modules, Pods |
Feedback rapide : job unitaire < 5 min en PR ; builds lourds en pré-merge ou nightly.
2.5 Le problème du Mac
iOS exige macOS. Solutions : runners GitHub macOS (coût), Mac mini self-hosted, Bitrise/Codemagic (mac géré), orbe MacStadium/GitLab.
3. Fastlane
3.1 Qu'est-ce que Fastlane ?
La boîte à outils d'automatisation mobile : un Fastfile décrit les étapes (lanes) ; Fastlane orchestre builds, tests, signing et distribution.
3.2 Les actions phares
| Action | Rôle |
|---|---|
| gym | Build/archive iOS (IPA) |
| scan | Tests iOS (xcodebuild) |
| match | Sync sécurisée des certificats/profiles |
| pilot | Upload TestFlight + gestion testeurs |
| deliver | Upload App Store Connect (metadata) |
| supply | Upload Play Console (AAB + metadata) |
| firebase_app_distribution | Upload Firebase App Distribution |
| gradle | Build Android via Gradle |
3.3 Exemple Fastfile (Android + iOS)
fastlane_version "2.220.0"
default_platform(:android)
platform :android do
desc "Build la release Android"
lane :build do |options|
version = options[:version]
gradle(
task: "assembleRelease",
properties: {
"VERSION_NAME" => version,
"VERSION_CODE" => ENV["BUILD_NUMBER"]
}
)
end
desc "Distribue aux testeurs internes"
lane :beta do
build
firebase_app_distribution(
app: "1:1234567890:android:abc123",
groups: "qa"
)
end
desc "Publie sur le Play Store (progressive)"
lane :promote do |options|
build
supply(
track: options[:track] || "production",
rollout: "0.10" # 10 % de rollout
)
end
end
platform :ios do
desc "Build + TestFlight"
lane :beta do
match(type: "appstore", readonly: true)
gym(scheme: "TaskFlow")
pilot(changelog: "Changelog automatisé")
end
desc "Publie sur l'App Store"
lane :release do
match(type: "appstore")
gym(scheme: "TaskFlow")
deliver(force: true)
end
end
3.4 Pourquoi Fastlane ?
- Une seule source de vérité pour le build (vs scripts shell disparates).
- match résout le signing iOS de façon déterministe.
- Intégration CI native (GitHub Actions, Bitrise, etc. peuvent appeler
fastlane <lane>). - Idempotent, testable localement.
3.5 Fastlane en CI
- name: Fastlane beta Android
run: bundle exec fastlane android beta
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
BUILD_NUMBER: ${{ github.run_number }}
4. Build configuration
4.1 Android — Build types et flavors
Build types (debug/release) : variantes de compilation.
Flavors (dimensions produit) : la même app en blanc avec URLs différentes.
// build.gradle.kts
android {
buildTypes {
getByName("debug") {
applicationIdSuffix = ".debug"
versionNameSuffix = "-debug"
}
getByName("release") {
isMinifyEnabled = true
}
}
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
}
create("prod") {
dimension = "environment"
}
}
}
Les combinaisons produisent : devDebug, stagingRelease, prodRelease… avec des URLs différentes :
buildConfigField("String", "API_URL", "\"https://api.dev.taskflow.app\"")
4.2 iOS — Schemes, configurations, targets
- Configurations : Debug / Release (build settings).
- Scheme : quel build (Debug/Release), quels tests, quels run.
- Target : unité de build ; une app peut avoir plusieurs targets (widget, extensions, share).
Xcode → Schemes → Edit Scheme → Run (Build Configuration: Debug)
L'équivalent des flavors Android côté iOS : des xcconfig ou des targets dupliquées (ou plus simple : une seule app + variables d'environnement).
4.3 Bonnes pratiques
- Séparer dev/staging/prod par URLs et identifiants, jamais par copie de code.
applicationIdSuffixet bundle id suffix pour pouvoir installer plusieurs versions côte à côte.- Toujours tester au moins une combinaison release en CI.
5. Signing
5.1 Android
| Clé | Rôle |
|---|---|
Keystore (.jks) | Clé privée de signature |
| Upload key | Signe l'AAB envoyé à Google |
| App signing by Google Play | Google gère la clé d'app, re-signature par play |
Protection : keystore dans un secret manager (GitHub Secrets), passwords en variables d'environnement, jamais dans le repo.
5.2 iOS
| Élément | Rôle |
|---|---|
| Certificat de distribution | Signe le binaire |
| Provisioning profile | Autorise l'installation (app + device/capabilities) |
| Signing & Capabilities (Xcode auto) | Gestion automatique |
| match (Fastlane) | Certificats/profiles chiffrés dans un repo privé |
5.3 Fastlane match
bundle exec fastlane match development
bundle exec fastlane match appstore
match :
- génère (si absents) certificat + profile ;
- les chiffre (clé passphrase) ;
- les stocke dans un repo privé (ou Google Cloud Storage/S3) ;
- les installe sur la machine CI/locale.
Toute l'équipe partage le même set → plus jamais « certificate not found ».
6. Versioning
6.1 SemVer
MAJOR.MINOR.PATCH
| Niveau | Incrément quand |
|---|---|
| MAJOR | Changement incompatible (breaking) |
| MINOR | Nouvelle fonctionnalité rétro-compatible |
| PATCH | Correction de bug rétro-compatible |
Android : versionName (SemVer) + versionCode (entier croissant obligatoire).
iOS : MARKETING_VERSION (SemVer) + CURRENT_PROJECT_VERSION (build number).
6.2 Le build number
- Android
versionCodeet iOSbuild numberdoivent croître strictement pour chaque upload. - En CI : utiliser le numéro de build (
github.run_number,CIRCLE_BUILD_NUM,BITRISE_BUILD_NUMBER).
// Android
android {
defaultConfig {
versionCode = (System.getenv("BUILD_NUMBER")?.toIntOrNull() ?: 1)
versionName = "1.4.2"
}
}
# iOS avec agvtool
agvtool new-version -all $BUILD_NUMBER
agvtool new-marketing-version -all 1.4.2
6.3 Automatiser
- Tag git
v1.4.2→ la CI publie la release. - Changelog généré à partir des PR merged (ex :
git log, Conventional Commits). - Incrément automatique du patch pour les corrections.
7. Distribution
7.1 Les canaux
| Canal | Public | Cas d'usage |
|---|---|---|
| Firebase App Distribution | Testeurs internes (Android+iOS) | Builds de dev/QA |
| TestFlight | Testeurs externes/iOS, beta public | Pré-release iOS |
| App Center (retraité 2025) | — | Historique, migrer |
| Play Console (tracks) | Public | Alpha/beta/production, staged rollout |
| App Store Connect | Public | App Store, phased release |
7.2 Firebase App Distribution
- name: Distribute
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_CREDENTIALS }}
groups: qa
file: app/build/outputs/apk/release/app-release.apk
7.3 Play Console — staged rollout
Lancer en 10 %, surveiller 24 h, pousser à 100 % (via supply avec rollout progressif). Permet de stopper avant que le bug n'atteigne tout le monde.
7.4 TestFlight
- 30 jours de validité par build (testeurs externes).
- Phased release sur l'App Store : 1 % → 10 % → 50 % → 100 %.
7.5 Les build numbers de distribution
Chaque plateforme impose un mapping build→version : garder un tableau de correspondance pour debugger.
8. Observabilité
8.1 Crash reporting
| Outil | Android/iOS/Cross |
|---|---|
| Firebase Crashlytics | Toutes plateformes |
| Sentry | Toutes plateformes |
| Bugsnag | Toutes plateformes |
Bonnes pratiques :
- Dé-obfusquer les stack traces (mapping R8 / dSYM).
- Enrichir : user id (anonymisé), version, device, breadcrumbs.
- Alerter sur les crash rates > seuil.
// Android — Crashlytics
FirebaseCrashlytics.getInstance().apply {
setCustomKey("last_screen", screenName)
setUserId(userId.takeLast(4)) // pseudonymisé
}
Crashlytics.crash() // test (optionnel)
// iOS — Sentry
SentrySDK.start { options in
options.dsn = "https://..."
options.environment = "production"
}
8.2 Analytics
- Firebase Analytics / Google Analytics 4 (standard).
- Événements de conversion et de parcours.
- Respect du consentement (chapitre 15 : ATT, GDPR).
8.3 APM — Application Performance Monitoring
| Outil | Métriques |
|---|---|
| Firebase Performance | Cold start, HTTP traces, screen traces |
| Sentry Performance | Transactions, spans |
| MetricKit (iOS) | Métriques système (Apple) |
// Firebase Performance — trace d'une opération
val trace = FirebasePerformance.getInstance().newTrace("load_tickets")
trace.start()
loadTickets()
trace.stop()
8.4 Le dashboard minimum
Diagramme en cours de génération...
| Métrique | Seuil d'alerte |
|---|---|
| Crash-free sessions | < 99 % → alerte |
| Cold start p95 | > 2.5 s → alerte |
| ANR rate | > 1 / 1000 sessions |
| HTTP 5xx | > 1 % → investigate |
8.5 Lier les outils à la CI
- Upload automatique du mapping R8 (Crashlytics) et des dSYM (Sentry/Xcode) après chaque build.
- Sentry release : associer le build au repo git.
9. Releases automatisées
9.1 Le workflow complet
Diagramme en cours de génération...
9.2 L'exemple GitHub Actions complet
name: Release
on:
push:
tags: ["v*"]
jobs:
android-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build AAB
run: ./gradlew bundleRelease
env:
VERSION_NAME: ${GITHUB_REF#refs/tags/v}
BUILD_NUMBER: ${{ github.run_number }}
- name: Distribute interne
uses: wzieba/Firebase-Distribution-Github-Action@v1
with:
appId: ${{ secrets.FIREBASE_APP_ID }}
serviceCredentialsFileContent: ${{ secrets.FIREBASE_CREDENTIALS }}
file: app/build/outputs/bundle/release/*.aab
- name: Upload mapping Crashlytics
run: ./gradlew uploadCrashlyticsMappingFileRelease
ios-release:
runs-on: macos-15
steps:
- uses: actions/checkout@v4
- name: Fastlane beta
run: bundle exec fastlane ios beta
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APP_STORE_CONNECT_API_KEY: ${{ secrets.AC_API_KEY }}
9.3 La mise en production manuelle assumée
Il est sain de garder un appui humain pour le passage au store (validate les screenshots, la conformité, le rollout). Automatiser TOUT jusqu'au « Promote », et garder un bouton.
9.4 Rollback
- Android/Play : pas de rollback réel → publier une version corrigée ; utiliser les tracks pour limiter le rollout.
- iOS : impossible de remplacer un build publié → ne jamais force-push un build cassé ; phased release.
10. Synthèse
Diagramme en cours de génération...
Points à retenir :
- La CI doit être rapide (unitaires en PR, builds lourds en release).
- Fastlane standardise le build et le signing (match).
- Le versioning SemVer + build number croissant est la base de tout.
- La distribution passe par des canaux (interne → QA → staged rollout).
- L'observabilité (crash, perf, analytics) est branchée sur la release.
- Les secrets (clés, certs) vivent dans un secret manager, jamais dans le repo.
Aller plus loin
- Chapitre 13 (tests en CI), 15 (signing & sécurité).
- Chapitre 18 (Projet-Fil-Rouge) : la chaîne DevOps complète de TaskFlow.