For a React Native app, when should CI hand the release build to Fastlane lanes or to EAS Build, and what stays in the CI job?
answer
- who owns the macOS machine
- who holds the signing keys
- bare project: lanes on your runner
- EXPO_TOKEN, --non-interactive, --no-wait
- CI keeps tests, gating and triggers
basics
~20 sHand off to Fastlane when you own the native projects and runners and want scripted signing and upload on them; hand off to EAS Build when Expo should run the builds and hold credentials. CI still tests, gates and triggers.
solid answer
~40 sThe choice turns on who owns the build machines and the signing keys. With Fastlane, the CI job prepares a runner (macOS for iOS), installs dependencies and calls a lane such as `bundle exec fastlane ios beta`; the lane builds, signs and uploads on your machine, which suits bare React Native projects with custom native code and teams that already keep signing assets. With EAS Build, the CI job only authenticates with `EXPO_TOKEN` and runs `eas build --platform all --non-interactive`, often with `--no-wait` and `--auto-submit`; Expo's servers run the builds and can manage keystores and certificates, which suits Expo projects and teams without a macOS fleet. In both cases CI still owns linting, tests, branch rules, which commit ships and the secrets for the hand-off itself.
code
bash · 9 lines# Expo project: CI triggers EAS Build and returns
npm ci
npx eas-cli build --platform all --profile production \
--non-interactive --no-wait
# Bare project: CI prepares the macOS runner, the lane does the rest
npm ci
(cd ios && bundle exec pod install)
bundle exec fastlane ios betago deeper
Recall that Fastlane automates build and upload steps on your own machine, while EAS Build runs the build on Expo's servers.
Explain the mechanics of each hand-off: a lane called through Bundler on a prepared runner, or eas build with EXPO_TOKEN, --non-interactive and --no-wait.
Show the judgement: who owns macOS machines and signing keys, what the CI job must still gate, and the failure modes of a non-interactive first run.
Weigh build infrastructure ownership against a hosted service across the portfolio: cost, credential custody, lock-in and how easily you can move away later.
## Three shapes of a React Native release job 1. **CI builds directly**: the job runs `./gradlew bundleRelease` and `xcodebuild archive` itself, then uploads. Everything is in the CI configuration. 2. **CI hands off to Fastlane**: the job prepares a runner and calls a lane. The lane, written in Ruby in the repository, performs build, signing and upload on that same runner. 3. **CI hands off to EAS Build**: the job calls the EAS CLI. The build runs on Expo's infrastructure, not on the CI runner. Shapes 2 and 3 exist because the release steps (signing, uploading, polling the store) are fiddly and are better written once in a tool than re-implemented in each CI system's YAML. ## What each hand-off moves out of CI | Concern | Fastlane lane | EAS Build | |---|---|---| | Where the build runs | your CI runner (macOS for iOS) | Expo's build servers | | Who keeps signing assets | you, or a certificate-sync tool | EAS can generate and store them | | Native projects | committed `android/` and `ios/` | committed, or generated by prebuild | | CI authenticates with | store API keys, sync-repo passphrase | `EXPO_TOKEN` | | CI minutes spent | the whole native build | a trigger, or a wait if you do not pass `--no-wait` | ## The Fastlane hand-off A **lane** is a named sequence of actions in `fastlane/Fastfile`. CI installs Ruby gems with Bundler and runs the lane, passing environment variables the lane reads. It fits when: - the app is a **bare React Native project** with custom native modules and Xcode or Gradle tweaks; - the team already runs **macOS runners** and wants builds on hardware it controls; - signing assets are already managed by the team, for example through a certificate-sync repository. CI still installs `node_modules` and runs `bundle exec pod install` before the lane, unless the lane does it. ## The EAS hand-off For an Expo project, the job needs only Node, the EAS CLI and an `EXPO_TOKEN` personal access token in the CI secret store: - `eas build --platform all --profile production --non-interactive` starts Android and iOS builds from the profile in `eas.json`. - `--no-wait` ends the CI step once the build is queued, so CI minutes are not spent waiting; drop it if a later step needs the artifact. - `--auto-submit` hands the finished build to EAS Submit with the submit profile of the same name. - `--freeze-credentials` stops a non-interactive run from changing stored credentials. Expo's guide asks for one interactive `eas build` from a developer machine first, so the project ID, `eas.json` and credentials exist before CI runs non-interactively. `eas build --local` runs the same build on your own machine, but the CLI marks it experimental. ## Deciding - **Expo project, no macOS fleet, small team**: EAS Build; CI triggers and gates. - **Bare project, heavy native customisation, existing macOS runners**: Fastlane lanes on your runners. - **Mixed**: EAS for store builds, while CI still runs unit tests and end-to-end tests on its own runners. For the gym-booking app, the deciding questions are concrete: who pays for and patches the macOS machines, who may hold the distribution certificate, and whether the native folders are hand-edited or generated. ## What never leaves CI - **Quality gates**: lint, type check, unit tests, and whatever end-to-end suite blocks a release. - **Release policy**: which branches or tags may ship, and approvals before a production build. - **The hand-off secret**: the `EXPO_TOKEN` or the store and sync credentials the lane needs, scoped to release jobs only. - **Traceability**: recording which commit, build number and build ID belong together, so a store build can be traced back to source. ## Failure modes at the seam A hand-off adds a boundary where problems hide: - **A green CI job with a failed build**: with `--no-wait`, CI reports success as soon as the build is queued. Someone must watch the EAS build result, or CI must wait for it. - **Drift between the lane and CI**: a lane that assumes `pod install` already ran fails on a new runner that skipped it. Make the lane idempotent or make its preconditions explicit. - **Credentials changed by automation**: a non-interactive EAS run that is allowed to repair credentials can alter what other builds use; `--freeze-credentials` prevents that from CI. - **Two sources of build numbers**: CI computing a number while EAS also auto-increments leads to confusing jumps; pick one owner.
- What does eas build --no-wait change for a React Native CI job?The step exits once EAS has queued the build, so the CI system does not bill minutes for the wait. The job then only proves the trigger succeeded; a later step that needs the artifact must run without `--no-wait` or poll separately.
- Why must an Expo project run eas build interactively once before CI can use it?The first interactive run creates the EAS project ID, writes `eas.json` build profiles, fills native identifiers in app config and sets up credentials. A non-interactive CI run cannot answer those prompts, so it fails until they exist.
- Does handing off to EAS Build remove the need for any secrets in CI?No. CI still holds `EXPO_TOKEN`, which can start builds and access stored credentials, so it needs the same scoping as signing keys. Store API keys may also be needed if credentials must be repaired from CI.
saying these in an interview costs you the question
- EAS Build runs on your CI runner, so you still need a macOS image
- A Fastlane lane removes the need for a macOS runner
- With EAS, CI needs no secrets at all
- Handing off means CI no longer runs the test suite
- eas build works non-interactively on a brand-new project