Field Notes

A Lightweight CI/CD Workflow for Expo Apps Using EAS & GitHub Actions

Jun '25

When you’re juggling multiple Expo apps — maybe one for a logistics tool, another for a small POS, and a few side experiments — the last thing you want is to spend hours building, uploading, or manually testing each change. I went through that pain for months: local builds breaking, mismatched environments, manual OTA updates, and “who built this version again?” moments.

Eventually, I decided to build a light, automated CI/CD workflow that would take care of everything: testing, previewing, and deploying — while staying fast and simple enough for small teams or solo developers.

Here’s how I did it.

Why Even Small Apps Deserve CI/CD

When I was rebuilding Kamioun’s mobile apps, every push meant a new release candidate to test. Doing that manually with Expo was fine for one app, but once I started building other projects — POS Light, Spearspots, internal tools — it quickly became unmanageable.

CI/CD became not about “being fancy,” but about speed and consistency.

I wanted:

  • Automatic preview builds for every PR.
  • One-click deployments to production via main.
  • OTA updates pushed through EAS Update with proper channels.
  • All builds and versions tracked without opening my laptop.

The Stack

  • Expo EAS for builds, submissions, and OTA updates.
  • GitHub Actions for CI/CD orchestration.
  • Expo tokens & secrets stored in GitHub repo secrets.
  • EAS channels & branches to separate environments (dev, staging, production).
  • (Optional) Slack notifications for build results.

The Core Workflow

When I push to main, GitHub Actions automatically:

  1. Runs lint, type checks, and lightweight tests.
  2. Builds for iOS and Android using EAS Build.
  3. Submits to stores (or to internal testers).
  4. Creates a new OTA update via EAS Update.

Here’s the minimal workflow file:

name: Build & Deploy Expo App

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - uses: expo/expo-github-action@v8
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: yarn install --frozen-lockfile
      - run: yarn lint && yarn tsc --noEmit
      - run: eas build --platform all --profile production --non-interactive
      - run: eas submit --platform all --non-interactive
      - run: eas update --channel production --non-interactive

That single file means no more manual builds — just push to main and watch EAS do the rest.

Preview Builds for Every Pull Request

This part changed everything.

Every PR now triggers a preview build on EAS with its own channel. It lets me or a teammate instantly test a branch before merging.

name: PR Preview Build

on:
  pull_request:
    branches:
      - main

jobs:
  preview:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - uses: expo/expo-github-action@v8
        with:
          eas-version: latest
          token: ${{ secrets.EXPO_TOKEN }}
      - run: yarn install --frozen-lockfile
      - run: eas build --profile preview --platform all --non-interactive
      - run: eas update --channel preview-${{ github.head_ref }} --non-interactive

Then, the Action posts a comment in the PR:

✅ Preview ready!
[iOS link](#) • [Android link](#)

This meant every contributor could open the app instantly in Expo Go, test the feature live, and merge with confidence.

Lightweight Testing

Before building, each run performs:

yarn lint
yarn tsc --noEmit

It’s minimal, but it catches most issues — broken imports, unused vars, or failing builds — early. For more complex apps, I add Detox end-to-end tests only for critical flows.

OTA Updates: Fix Bugs in Minutes

For solo projects, EAS Update is a game-changer.

If I find a small bug post-deploy, I just push a patch to the production channel:

eas update --branch main --channel production

No rebuild, no resubmit — it’s live for all users in under 60 seconds. This workflow has saved me countless hours, especially during app store reviews.

The Result

I now ship faster than ever:

  • PR → preview build in ~5 minutes.
  • Main push → production build & OTA in ~10 minutes.
  • No manual steps.

For small teams or indie devs working across multiple apps, this setup strikes the perfect balance: automated, transparent, and light enough to maintain.

In the end, CI/CD isn’t about having fancy pipelines — it’s about not thinking about pipelines at all. When everything runs smoothly, you’re free to focus on what matters: building great mobile experiences.