From Commit to App Store: A React Native CI/CD Playbook
EAS Build, Fastlane, code signing, and store submission — a practical guide to automating the boring parts of shipping mobile apps, with a working setup you can copy.
The first time you ship a React Native app to the App Store, you discover a world of pain that web developers never have to think about. Certificates, provisioning profiles, keystores, build numbers, version codes. Each platform has its own signing dance, and getting it wrong means a rejected submission or, worse, an app that crashes on launch.
After doing this for a dozen apps, here's the CI/CD setup I'd build today.
EAS Build is the new default
Expo's EAS (Expo Application Services) is the easiest way to build React Native apps in the cloud. You push a commit, EAS builds it for iOS and Android, signs it, and produces an IPA and APK you can install on a device.
The killer feature is EAS Submit — it takes the build artifacts and submits them to TestFlight and the Play Store automatically. You can go from git push to a TestFlight build in 15 minutes, with no manual signing.
EAS works with bare React Native projects too, not just Expo. You add an eas.json to your repo, configure your build profiles, and you're set up. The free tier is enough for indie developers; the paid tier is reasonable for teams.
When you need Fastlane
EAS handles 90% of the use case. The remaining 10% is when you need more control — custom build steps, internal distribution to enterprise users, or integration with a CI system that isn't EAS.
Fastlane is the traditional choice. It has plugins for almost everything: match for code signing, gym for builds, pilot for TestFlight, supply for Play Store. The configuration is Ruby, which is its own adventure, but the result is a fully automated build pipeline.
I'd recommend starting with EAS and only reaching for Fastlane when you hit a wall. Most teams never need to.
Code signing, demystified
iOS code signing is the most confusing part. You need a certificate (who you are), a provisioning profile (what you're allowed to do), and a bundle ID (what app you're signing). Apple's terminology is unhelpful and the error messages are worse.
EAS handles this for you with eas credentials. You run the command, it creates the certificate and profile in your Apple Developer account, and stores them in EAS's secure storage. You never see them, and you don't need to.
If you're using Fastlane, the match plugin syncs your certificates and profiles via a private Git repo. It's a bit of a setup, but once it works, it works everywhere — locally, in CI, on every machine.
Android is simpler. You generate a keystore once, store the password somewhere safe (a secret manager, not in the repo), and you're done. The keystore lasts the lifetime of your app, so you only need to do this once.
Versioning strategy
The convention I follow: version in package.json is the user-facing version (1.2.3), and buildNumber / versionCode is the build number that increments with every release.
For iOS, the build number is in the Info.plist (CFBundleVersion) and must be unique. For Android, it's versionCode in build.gradle and also must be unique. The App Store and Play Store will reject duplicate build numbers.
EAS can auto-increment both for you, based on the build number it tracks internally. If you're using Fastlane, you'll need to bump these manually in your build script.
The real win
The point of all this isn't to ship faster — it's to ship with confidence. When your CI pipeline produces a signed, tested, store-ready build from every commit, you stop being afraid of releases.
You push, the build runs, the tests pass, and you have a build to submit. That changes how you think about shipping. Instead of a big-bang release every few weeks, you can do small releases every day. The bugs that escape are smaller. The fixes are faster. The team stops dreading release day.