# Setting Up CI/CD for React Native Apps with EAS Build and GitHub Actions

> How to configure a production-grade CI/CD pipeline for React Native using EAS Build and GitHub Actions — automated builds, testing, and store deployment.

- **Published:** 2025-05-12
- **Author:** Clixo
- **Reading time:** 6 min read
- **Tags:** react-native, ci-cd, eas-build, github-actions, devops
- **Canonical URL:** https://clixo.sh/blog/react-native-ci-cd-eas-build-setup

Manually building and submitting React Native apps is a process that breaks under team pressure. One developer owns the signing certificates. Builds only run on their machine. A new hire cannot push a fix to production. This is not a deployment pipeline — it is a bottleneck with a name on it.

A proper CI/CD setup for React Native takes a day or two to configure correctly and saves orders of magnitude more time over the product lifecycle. Here is how to set one up using EAS Build and GitHub Actions.

## Why EAS Build Over Self-Hosted Builds

The primary reason teams avoid self-hosted iOS builds is the infrastructure cost: you need a macOS machine, Xcode licenses, Apple provisioning certificates configured correctly, and build agents that stay current with OS and Xcode updates.

EAS Build moves this infrastructure to Expo's cloud. You get:

- iOS and Android builds without a Mac for iOS
- Automatic credential management (EAS Credentials handles certificates and provisioning profiles)
- Build caching that speeds up subsequent builds
- A defined build profile system (`eas.json`) that makes build configurations reproducible

The trade-off is per-build cost on paid plans and some loss of control over the build machine configuration. For most product teams, this trade-off is correct.

## Project Prerequisites

Before setting up CI/CD:

- Your project should be using Expo (managed or bare with EAS support)
- EAS CLI installed: `npm install -g eas-cli`
- An Expo account and the project linked: `eas init`
- iOS credentials configured: `eas credentials` for iOS, which walks you through certificate and profile creation
- Android keystore configured: `eas credentials` for Android

Store the EAS token as a GitHub Actions secret. In your Expo account dashboard, generate a token under Access Tokens. Add it to your GitHub repository's Settings → Secrets → Actions as `EXPO_TOKEN`.

## Configuring eas.json

`eas.json` defines your build profiles. A standard configuration:

```json
{
  "cli": {
    "version": ">= 7.0.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "ios": {
        "simulator": false
      }
    },
    "production": {
      "autoIncrement": true
    }
  },
  "submit": {
    "production": {
      "ios": {
        "appleId": "your-apple-id@example.com",
        "ascAppId": "your-app-store-connect-app-id",
        "appleTeamId": "YOUR_TEAM_ID"
      },
      "android": {
        "serviceAccountKeyPath": "./google-service-account.json",
        "track": "internal"
      }
    }
  }
}
```

Profile breakdown:

- `development`: creates a development client build for testing with native modules. Distributed internally (not through the store).
- `preview`: a production-like internal build for QA and stakeholder review. No simulator — installs on a real device.
- `production`: the store release build. `autoIncrement: true` lets EAS increment the build number automatically on each build.

## GitHub Actions Workflow

Create `.github/workflows/build.yml`:

```yaml
name: EAS Build

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm test

  build-preview:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - uses: expo/expo-github-action@v8
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: eas build --platform all --profile preview --non-interactive
```

Add a separate workflow for production releases triggered by a version tag:

```yaml
name: Release

on:
  push:
    tags:
      - 'v*'

jobs:
  build-and-submit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - uses: expo/expo-github-action@v8
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: eas build --platform all --profile production --non-interactive
      - run: eas submit --platform all --profile production --non-interactive
```

This workflow builds for both platforms and submits to the stores automatically on every version tag push.

## Over-the-Air Updates with EAS Update

For JavaScript-only changes (no new native code), EAS Update lets you push updates to users without a store release:

```yaml
- name: Push OTA update
  run: eas update --branch production --message "Fix: ${{ github.event.head_commit.message }}"
```

Add this step to your main branch workflow for fast iteration between full releases. Users receive the update on next app launch or background refresh.

The EAS Update channel system lets you target specific audiences: the `production` channel reaches all users, while a `preview` channel reaches only internal testers running the preview build.

## Handling Secrets in CI

Your app likely needs API keys and environment-specific configuration. Do not commit these to source control. Options:

- **EAS Secrets**: `eas secret:create --name API_KEY --value yourvalue` — available as environment variables during the EAS build
- **GitHub Actions secrets**: available to your workflow scripts as `${{ secrets.MY_SECRET }}`
- **app.config.js with environment variables**: if you use `app.config.js` instead of `app.json`, you can read `process.env` during the build to inject configuration dynamically

## Testing in CI

Before your builds, run:

```bash
npx jest --coverage --passWithNoTests
npx tsc --noEmit
```

TypeScript errors caught in CI are cheaper than TypeScript errors caught after a two-hour EAS build. Run type checking as a separate fast job that gates the build job.

For E2E testing on a CI build, Detox can run against your preview build on a remote iOS simulator or Android emulator via services like AWS Device Farm or BrowserStack App Automate. This is worth adding once your test suite is stable enough to not produce constant false failures.

## The Pipeline in Summary

```mermaid
flowchart TD
  A["Pull Request opened"] --> B["Run tests and type check"]
  B --> C{"Merge to main?"}
  C -->|Yes| D["EAS Build — preview profile"]
  D --> E["Push OTA update to preview channel"]
  C -->|No| F["PR feedback only"]
  E --> G{"Version tag pushed?"}
  G -->|Yes| H["EAS Build — production profile"]
  H --> I["Submit to App Store and Play Store"]
  I --> J["Push OTA to production channel"]
```

- **Every PR**: run tests and type check
- **Every merge to main**: build preview for iOS and Android, push OTA update to `preview` channel
- **Every version tag**: build production for iOS and Android, submit to stores, push OTA update to `production` channel

This pipeline covers the full delivery workflow without requiring any engineer to own a local build machine or manage certificates manually.

If your team is setting up infrastructure for a React Native product and needs engineering support on the CI/CD and build system, [Clixo handles the full delivery pipeline](https://clixo.sh/#contact) as part of our product builds.

---

Clixo · 1141 W Bryn Mawr Ave, Itasca, IL 60143, US · [hello@clixo.sh](mailto:hello@clixo.sh)
[Start a build](https://clixo.sh/#contact) · [All services](https://clixo.sh/services) · [Agent guide (llms.txt)](https://clixo.sh/llms.txt)
