MFormations
Modern Mobile Engineering

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

  1. La chaîne DevOps mobile
  2. CI/CD
  3. Fastlane
  4. Build configuration
  5. Signing
  6. Versioning
  7. Distribution
  8. Observabilité
  9. 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

EnvironnementBuildDistribué àSigné avec
DebugdevDéveloppeursClé debug
Staging/TestFlighttestQA, stakeholdersCertif dev/ad hoc
ProductionreleasePublicCertif 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

PlateformePoints fortsCas d'usage
GitHub ActionsGratuit (open source), écosystème, matricesStandard 2026
BitriseOrientée mobile, steps prêtesÉquipes mobiles pures
GitLab CIIntégré à GitLab, runners autoTout GitLab
CodemagicFlutter-first, iOS sans MacFlutter
CircleCIRapide, orchestration fineiOS/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

PlateformeCache
Android~/.gradle, ~/.konan
iOSDerivedData, SPM cache
Flutter~/.pub-cache
RNnode_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

ActionRôle
gymBuild/archive iOS (IPA)
scanTests iOS (xcodebuild)
matchSync sécurisée des certificats/profiles
pilotUpload TestFlight + gestion testeurs
deliverUpload App Store Connect (metadata)
supplyUpload Play Console (AAB + metadata)
firebase_app_distributionUpload Firebase App Distribution
gradleBuild 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.
  • applicationIdSuffix et 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 keySigne l'AAB envoyé à Google
App signing by Google PlayGoogle 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émentRôle
Certificat de distributionSigne le binaire
Provisioning profileAutorise 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 :

  1. génère (si absents) certificat + profile ;
  2. les chiffre (clé passphrase) ;
  3. les stocke dans un repo privé (ou Google Cloud Storage/S3) ;
  4. 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

NiveauIncrément quand
MAJORChangement incompatible (breaking)
MINORNouvelle fonctionnalité rétro-compatible
PATCHCorrection 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 versionCode et iOS build number doivent 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

CanalPublicCas d'usage
Firebase App DistributionTesteurs internes (Android+iOS)Builds de dev/QA
TestFlightTesteurs externes/iOS, beta publicPré-release iOS
App Center (retraité 2025)Historique, migrer
Play Console (tracks)PublicAlpha/beta/production, staged rollout
App Store ConnectPublicApp 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

OutilAndroid/iOS/Cross
Firebase CrashlyticsToutes plateformes
SentryToutes plateformes
BugsnagToutes 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

OutilMétriques
Firebase PerformanceCold start, HTTP traces, screen traces
Sentry PerformanceTransactions, 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étriqueSeuil 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 :

  1. La CI doit être rapide (unitaires en PR, builds lourds en release).
  2. Fastlane standardise le build et le signing (match).
  3. Le versioning SemVer + build number croissant est la base de tout.
  4. La distribution passe par des canaux (interne → QA → staged rollout).
  5. L'observabilité (crash, perf, analytics) est branchée sur la release.
  6. 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.