Workflows Expo CI/CD (EAS) : builds intelligents, fingerprint et OTA Type: blog post Language: fr-FR Canonical URL: https://simonboisset.com/blog/expo-ci-cd-workflows-fingerprint-ota Published: 12 janvier 2026 Summary: 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 "... 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 preview main -> 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 getandroidbuild: name: Check existing Android build needs: [fingerprint] type: get-build environment: production params: fingerprinthash: ${{ needs.fingerprint.outputs.androidfingerprinthash }} profile: production getiosbuild: name: Check existing iOS build needs: [fingerprint] type: get-build environment: production params: fingerprinthash: ${{ needs.fingerprint.outputs.iosfingerprinthash }} profile: production buildandroid: name: Build Android needs: [getandroidbuild] if: ${{ !needs.getandroidbuild.outputs.buildid }} type: build environment: production params: platform: android profile: production buildios: name: Build iOS needs: [getiosbuild] if: ${{ !needs.getiosbuild.outputs.buildid }} type: build environment: production params: platform: ios profile: production submitandroid: name: Submit Android needs: [buildandroid] if: ${{ needs.buildandroid.outputs.buildid }} type: submit environment: production params: buildid: ${{ needs.buildandroid.outputs.buildid }} profile: production submitios: name: Submit iOS needs: [buildios] if: ${{ needs.buildios.outputs.buildid }} type: submit environment: production params: buildid: ${{ needs.buildios.outputs.buildid }} profile: production updateandroid: name: Update Android needs: [getandroidbuild, getiosbuild] if: ${{ needs.getandroidbuild.outputs.buildid && !needs.getiosbuild.outputs.buildid }} type: update environment: production params: channel: production platform: android updateios: name: Update iOS needs: [getandroidbuild, getiosbuild] if: ${{ needs.getiosbuild.outputs.buildid && !needs.getandroidbuild.outputs.buildid }} type: update environment: production params: channel: production platform: ios updateall: name: Update All needs: [getandroidbuild, getiosbuild] if: ${{ needs.getandroidbuild.outputs.buildid && needs.getiosbuild.outputs.buildid }} 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 (buildNumber iOS / versionCode Android) : 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. Related links Blog index: https://simonboisset.com/blog Website: https://simonboisset.com/