Skip to content
Latchkey

CI/CD for an Expo EAS Build with GitHub Actions

Lint, test, and kick off cloud EAS builds for both platforms from CI.

Expo Application Services (EAS) builds your native binaries in the cloud, so CI mainly validates the JS and triggers the build. This recipe lints and tests, then starts non-interactive EAS builds.

What the pipeline does

  • install deps with npm ci
  • lint with eslint and type-check
  • test with jest
  • set up the EAS CLI with a token
  • trigger eas build --platform all --non-interactive

The workflow

expo/expo-github-action installs the EAS CLI and authenticates with EXPO_TOKEN. The build runs on EAS infrastructure; CI only triggers it and waits for the result.

.github/workflows/ci.yml
name: CI
on:
  push:
    branches: [main]
  pull_request:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - uses: expo/expo-github-action@v8
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: npm ci
      - run: npm run lint --if-present
      - run: npm test --if-present
      - if: github.ref == 'refs/heads/main'
        run: eas build --platform all --non-interactive --no-wait

Caching and speed

cache: npm restores the JS install; the native compile happens on EAS, not the runner, so CI stays light. The JS validation runs frequently; cheaper managed runners such as Latchkey keep those runs inexpensive and auto-retry transient registry or EAS-trigger failures.

Deploying

After a successful build, run eas submit --platform all to push binaries to the App Store and Play Store, or use eas update to ship an OTA JS update without a new native build. Gate submit on a tag or manual approval.

Making this reliable in CI

  • Pin every tool version. An unpinned toolchain turns a runner image update into a build break on unchanged code.
  • Cache what is expensive to produce, and key the cache to the tool version so a restore across a version boundary cannot poison the build.
  • Assert on the produced artifact rather than on the command succeeding.
  • Set an explicit timeout so a hung step fails fast instead of consuming the whole job budget.

Key takeaways

  • EAS compiles natives in the cloud; CI just validates and triggers.
  • Authenticate with EXPO_TOKEN via the Expo GitHub action.
  • Use eas update for OTA JS changes without a native rebuild.

Frequently asked questions

CI/CD for an Expo EAS Build with GitHub Actions?
Expo Application Services (EAS) builds your native binaries in the cloud, so CI mainly validates the JS and triggers the build. This recipe lints and tests, then starts non-interactive EAS builds.
The workflow?
expo/expo-github-action installs the EAS CLI and authenticates with EXPO_TOKEN. The build runs on EAS infrastructure; CI only triggers it and waits for the result.
Caching and speed?
cache: npm restores the JS install; the native compile happens on EAS, not the runner, so CI stays light. The JS validation runs frequently; cheaper managed runners such as Latchkey keep those runs inexpensive and auto-retry transient registry or EAS-trigger failures.
Deploying?
After a successful build, run eas submit --platform all to push binaries to the App Store and Play Store, or use eas update to ship an OTA JS update without a new native build. Gate submit on a tag or manual approval.

Related guides

References

Run this faster and cheaper on Latchkey managed runners - self-healing included. Start free → 30-day trial · No credit card