Workflows Expo CI/CD (EAS) : builds intelligents, fingerprint et OTA
12 janvier 2026
Workflows Expo CI/CD (EAS) : builds intelligents, fingerprint et OTA
Dans un projet Expo/React Native, la vraie question n'est pas "comment builder ?" mais "quand faut-il vraiment builder ?". Les builds natifs sont lents (et coûteux en CI), alors que les updates OTA sont rapides... mais ne couvrent pas tous les changements.
L'objectif du workflow : automatiser toute la chaîne (builds natifs, OTA, soumissions) tout en évitant les builds inutiles, grâce au fingerprint Expo et à un versioning explicite.
Structure type : staging + production
Deux environnements, un seul flux :
dev/staging-> environnement previewmain-> environnement production
Même logique dans les deux cas :
- calculer un fingerprint
- décider entre build natif ou OTA
- publier automatiquement
En production, on ajoute la soumission store lorsqu'un build natif a eu lieu.
Fingerprint : décider automatiquement
Le fingerprint est un hash basé sur ce qui impacte vraiment le binaire natif :
- dépendances et modules natifs
- config Expo + plugins
- fichiers de config
- version marketing (si injectée dans
app.config)
Règle simple :
- Fingerprint identique à un build existant -> OTA
- Fingerprint différent -> build natif
Exemple de workflow EAS (générique)
Voici un exemple inspiré d'un workflow réel, mais volontairement générique. Adapte les paths, profile et channel à ton projet.
name: Production builds
on:
push:
branches:
- main
tags:
- v*.*.*
- "!v*.*.*-**"
paths:
- apps/mobile/**
- packages/**
- "!**/*.md"
jobs:
fingerprint:
name: Fingerprint
type: fingerprint
environment: production
get_android_build:
name: Check existing Android build
needs: [fingerprint]
type: get-build
environment: production
params:
fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
profile: production
get_ios_build:
name: Check existing iOS build
needs: [fingerprint]
type: get-build
environment: production
params:
fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }}
profile: production
build_android:
name: Build Android
needs: [get_android_build]
if: ${{ !needs.get_android_build.outputs.build_id }}
type: build
environment: production
params:
platform: android
profile: production
build_ios:
name: Build iOS
needs: [get_ios_build]
if: ${{ !needs.get_ios_build.outputs.build_id }}
type: build
environment: production
params:
platform: ios
profile: production
submit_android:
name: Submit Android
needs: [build_android]
if: ${{ needs.build_android.outputs.build_id }}
type: submit
environment: production
params:
build_id: ${{ needs.build_android.outputs.build_id }}
profile: production
submit_ios:
name: Submit iOS
needs: [build_ios]
if: ${{ needs.build_ios.outputs.build_id }}
type: submit
environment: production
params:
build_id: ${{ needs.build_ios.outputs.build_id }}
profile: production
update_android:
name: Update Android
needs: [get_android_build, get_ios_build]
if: ${{ needs.get_android_build.outputs.build_id && !needs.get_ios_build.outputs.build_id }}
type: update
environment: production
params:
channel: production
platform: android
update_ios:
name: Update iOS
needs: [get_android_build, get_ios_build]
if: ${{ needs.get_ios_build.outputs.build_id && !needs.get_android_build.outputs.build_id }}
type: update
environment: production
params:
channel: production
platform: ios
update_all:
name: Update All
needs: [get_android_build, get_ios_build]
if: ${{ needs.get_android_build.outputs.build_id && needs.get_ios_build.outputs.build_id }}
type: update
environment: production
params:
channel: production
platform: all
Versioning : version marketing explicite, builds auto-incrémentés
Je sépare volontairement :
- version marketing (lisible, intentionnelle) : gérée par nous
- numéros de build (
buildNumberiOS /versionCodeAndroid) : auto-incrémentés par EAS
La version marketing est stockée dans package.json :
{
"name": "my-app",
"version": "1.4.0"
}
Et injectée dans app.config.ts :
import type { ConfigContext, ExpoConfig } from "expo/config";
import pkg from "./package.json";
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
version: pkg.version,
});
Conclusion & accompagnement
Ce type de pipeline réduit fortement les coûts de build, accélère les cycles de livraison et sécurise les mises en production grâce à des règles simples et automatisées.
👉 Vous souhaitez mettre en place ou optimiser un workflow Expo CI/CD (fingerprint, OTA, versioning, soumissions automatiques) ?
Je vous accompagne sur l'architecture, la configuration EAS, la CI et les bonnes pratiques Expo/React Native.
➡️ Prenez rendez-vous avec moi pour construire un pipeline adapté à votre application.
Je suis Simon Boisset, développeur mobile/full-stack. J'aide les équipes à livrer des apps React Native/Expo, remettre à plat des stacks legacy et sécuriser les releases.