with GitHub Actions and EAS
A solid CI/CD pipeline turns every merge into a reproducible, signed build, instead of relying on manually built binaries from individual developer machines. This article shows how GitHub Actions and Expo Application Services together automate lint, tests, cloud builds, code signing and store submission for a React Native app.
Table of Contents
- 1. Why a CI/CD pipeline is essential for React Native
- 2. GitHub Actions workflow structure: triggers and jobs
- 3. Running lint, typecheck and tests automatically
- 4. EAS Build: native binaries without a local Xcode
- 5. Build profiles in eas.json: development, preview, production
- 6. Code signing and credentials management via EAS
- 7. EAS Submit and EAS Update for delivery
- 8. Caching and branch strategy for faster pipelines
- 9. Local builds compared to EAS Build in the cloud
- 10. Summary
- 11. FAQ
1. Why a CI/CD pipeline is essential for React Native
Without a working CI/CD pipeline, every release of a React Native app depends on a single developer machine: the right Xcode version, the matching certificates, a clean node_modules install. One single misconfigured machine is enough to produce a build that cannot be reproduced or subtly behaves differently from the last release. An automated pipeline removes this dependency on individual machines entirely.
For React Native there is an added wrinkle: unlike a pure web app, a release needs native compilation for iOS and Android, including code signing with real certificates. This is exactly where EAS comes in, Expo's cloud infrastructure for builds, submissions and over-the-air updates, combined with GitHub Actions as the orchestration layer for triggers, tests and release logic.
Building a CI/CD pipeline with GitHub Actions and EAS already pays off for solo projects, since it catches errors before release that would otherwise only surface during App Store review, or worse, with real users. For teams of two or more developers it quickly becomes mandatory, since otherwise nobody can reliably say which code actually ended up in which published build.
2. GitHub Actions workflow structure: triggers and jobs
A GitHub Actions workflow for React Native typically splits into several jobs: a fast lint and typecheck job that runs on every push, a test job for unit and integration tests, and a significantly slower build job that only runs on specific events, such as a merge into the main branch or a manual trigger. This staging prevents every small commit from triggering a full, multi-minute cloud build.
Trigger configuration through on: push and on: pull_request controls when which job runs, while workflow_dispatch allows a manual start through the GitHub interface, for example for a targeted preview build to test a single feature. Matrix jobs additionally allow tests to run in parallel against multiple Node versions or operating system runners, which is especially useful for catching incompatibilities early in cross-platform libraries.
name: React Native CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: yarn
- run: yarn install --frozen-lockfile
- run: yarn lint
- run: yarn typecheck
- run: yarn test --ci --coverage
eas-build-preview:
needs: lint-and-test
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm install -g eas-cli
- run: eas build --platform all --profile preview --non-interactive
env:
# EXPO_TOKEN is injected here from a GitHub repository secret of the same name
EXPO_TOKEN: FROM_REPOSITORY_SECRET
3. Running lint, typecheck and tests automatically
Before a native build is even triggered, the CI/CD pipeline should run the cheap, fast checks first: ESLint for style rules, TypeScript type checking for structural errors, Jest for unit tests of the business logic. These steps typically run in under two minutes and catch the vast majority of all errors before expensive cloud build minutes get consumed.
For end-to-end smoke tests with Detox or Maestro, ordering matters: these tests need a finished, native build, so they run only after a successful EAS build step and typically not on every pull request, but specifically before a production release. A sensible compromise is running E2E smoke tests nightly or on every merge into the main branch, instead of on every single commit, since they run considerably longer than plain unit tests.
// checkoutFlow.test.tsx — Jest unit test run on every push in CI
import { render, screen } from '@testing-library/react-native';
import { CheckoutSummary } from '../CheckoutSummary';
describe('CheckoutSummary', () => {
it('shows the total price formatted with currency', () => {
render(<CheckoutSummary items={[{ price: 19.99, qty: 2 }]} />);
expect(screen.getByText('39.98 EUR')).toBeTruthy();
});
it('disables the pay button while a payment request is in flight', () => {
render(<CheckoutSummary items={[]} isSubmitting />);
expect(screen.getByRole('button', { name: /pay/i })).toBeDisabled();
});
});
4. EAS Build: native binaries without a local Xcode
EAS Build solves a problem every React Native team knows: native iOS builds need a Mac with Xcode installed, which in classic CI environments requires either expensive Mac runners or complicated Hackintosh setups. EAS Build takes over the full native compilation in Expo's own cloud infrastructure, for both iOS and Android, so the GitHub Actions runner itself only triggers the eas build command and waits for the finished result, without needing native toolchains of its own.
This architecture also means a build can be triggered on a cheap Linux runner, even when the result is an iOS binary. The actual compilation happens entirely within EAS's managed infrastructure, including the correct Xcode version matching the respective Expo SDK. For teams without their own Mac hardware, this is often the decisive reason to adopt EAS at all, instead of operating their own Mac Mini build farm.
5. Build profiles in eas.json: development, preview, production
The eas.json file defines build profiles, each representing different environment variables, signing settings and distribution channels. A development profile produces a debug build with the dev client enabled for local development, a preview profile an internal distribution build for testers via TestFlight or direct APK installation, a production profile the final, store-bound release build with production API endpoints.
The decisive advantage of these profiles in a CI/CD pipeline: a pull request can automatically trigger a preview build against the staging API, while a merge into main produces a production build against the real API base URL, without any manual switching anywhere in the code. Environment variables live directly in the respective profile or in EAS secrets, and stay cleanly separated from the actual codebase.
{
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"env": { "API_URL": "https://dev-api.example.com" }
},
"preview": {
"distribution": "internal",
"channel": "preview",
"env": { "API_URL": "https://staging-api.example.com" }
},
"production": {
"autoIncrement": true,
"channel": "production",
"env": { "API_URL": "https://api.example.com" }
}
},
"submit": {
"production": {}
}
}
6. Code signing and credentials management via EAS
Code signing has historically been one of the biggest sources of failure in mobile releases: expired certificates, mismatched provisioning profiles, keystores accidentally committed to the git repository. EAS manages these credentials centrally in an encrypted store, either fully generated and managed by EAS itself, or as your own uploaded certificates, if the team already has an existing Apple developer account with existing provisioning profiles.
In the CI pipeline itself, these credentials never appear in plaintext. The GitHub Actions runner authenticates only through an EXPO_TOKEN, stored as an encrypted repository secret, and EAS Build then loads the matching certificates and keystores server-side from its own secured storage. That significantly reduces the attack surface compared to a classic setup where signing files travel as CI artifacts through multiple systems.
# Configure credentials interactively once, EAS stores them securely afterward
eas credentials
# List currently stored credentials for a specific platform
eas credentials --platform ios
# In CI, authentication happens purely through the EXPO_TOKEN environment variable
# stored as a GitHub repository secret, never through interactive prompts
export EXPO_TOKEN="your-expo-access-token-here"
eas build --platform ios --profile production --non-interactive
7. EAS Submit and EAS Update for delivery
After a successful production build, eas submit handles the automatic upload to App Store Connect or the Google Play Console, including the necessary API authentication through App Store Connect API keys or a Google service account. That fully replaces manually uploading through Transporter or the Play Console interface, and turns submission into a single, repeatable pipeline step.
EAS Update adds a second delivery mode to this process: for pure JavaScript bundle changes that require no new native compilation, for example a text fix or a small logic change, an update can be delivered directly to already installed apps, without going through a full store review cycle. This is not a replacement for native releases, but a valuable tool for fixing small bugs in minutes instead of days.
8. Caching and branch strategy for faster pipelines
A CI pipeline that fully reinstalls node_modules on every run wastes time and CI minutes unnecessarily. GitHub Actions' built-in caching through actions/setup-node with cache: yarn, or an explicit actions/cache step for the yarn or npm cache folder, often reduces install time from several minutes down to a few seconds when dependencies have not changed.
A clear branch strategy rounds out the CI/CD pipeline: feature branches trigger only lint, typecheck and unit tests on every push, a preview build is created specifically on a pull request against main, and only a merge into main triggers the full production build followed by EAS Submit. This staging keeps the pipeline fast for day to day development and reserves the expensive, slow steps for actual release candidates.
9. Local builds compared to EAS Build in the cloud
Whether migrating from local, manually run builds to a fully automated EAS pipeline pays off shows most clearly in a direct comparison of the most important operational dimensions.
| Dimension | Local builds (manual) | EAS Build in the cloud pipeline |
|---|---|---|
| Setup effort | Xcode, Android Studio, certificates per machine | One eas.json, no local toolchain needed |
| Reproducibility | Depends on the state of the respective machine | Identical, versioned build environment |
| Signing | Manual certificate management, error-prone | Centrally managed, never in plaintext in CI |
| Parallelization | One build blocks the developer's machine | Multiple builds in parallel in the cloud |
| Team scaling | Every developer needs the full setup | One central pipeline access for everyone |
For small prototypes a local build may still be sufficient, but as soon as a team grows or a release regularly goes to a store, the advantages of a cloud pipeline almost always outweigh the initial setup time. The biggest, often underestimated win is not speed, but traceability: every build can be traced back exactly to a commit and a pipeline run.
Mironsoft
React Native development, CI/CD pipelines and app release automation
Building a CI/CD pipeline for your React Native app?
We set up GitHub Actions and EAS for your team, including build profiles, code signing, automated store submission and EAS Update for fast bug fixes without a full review cycle.
Workflow design
GitHub Actions workflows with a sensible trigger and job structure
EAS configuration
Build profiles, credentials management and submission automation
Release strategy
Branch strategy, caching and EAS Update rollout planning
10. Summary
A working React Native CI/CD pipeline with GitHub Actions and EAS starts with a clear job staging: fast lint and typecheck checks on every push, targeted preview builds on pull requests, full production builds only on merges into main. EAS Build handles the actual native compilation in the cloud, without a team needing its own Mac hardware for iOS builds, and build profiles in eas.json cleanly separate development, preview and production through their own environment variables.
Code signing, historically one of the biggest sources of failure in mobile releases, is defused by EAS's central credentials management, since certificates and keystores never travel through the CI pipeline in plaintext. EAS Submit automates store submission, EAS Update enables fast bug fixes without a full review cycle. Together this creates a pipeline in which every release can be traced back exactly to a commit and reproduced at any time.
React Native CI/CD Pipeline — The Essentials at a Glance
Job staging
Fast checks on every push, expensive cloud builds only on pull requests and main merges.
EAS Build
Native compilation in the cloud, no own Mac needed for iOS builds.
Code signing
Centrally managed by EAS, certificates never appear in plaintext in the pipeline.
Submit & Update
Automated store submission plus over-the-air fixes without a full review cycle.