Automating Local React Native Expo EAS Builds for Android, iOS  and Web

In this article I want to share a simple example setup for automating local React Native Expo builds with EAS for Android, iOS, and Web. The workflow demonstrates how to: automatically generate platform specific builds, install generated .apk file on all connected Android devices, deploy .ipa file to all connected iOS devices, and spin up a Docker container to serve the web build.

The core idea of this setup is to provide simple automation for preview test builds. However, it can be easily adapted or extended to support production builds, automated distribution, and additional workflows.

All scripts, configurations and docker files can be found in GitHub repository: https://github.com/AndreiMaksimovich/react-native-expo-eas-local-build-automation

Why you should automate building process for expo eas

Independence

By automating local EAS builds, you’re not relying solely on Expo’s cloud infrastructure. Even if the cloud service has downtime, pricing changes, or limitations, your build pipeline keeps running.

Secure Handling of Certificates, API Keys, and Secrets

Automated local builds allow you to store API keys, signing certificates, and production secrets securely outside of project repositories and development machines. Instead, they’re injected during the build process reducing the risk of leaks and keeping sensitive data under control.

Speed

Local automated builds can be significantly faster. You avoid waiting in cloud queues and gain more control over resource allocation.

Adjustability

When you own the build pipeline, you can extend it with custom routines, for example:

  • running automated tests before building
  • asset analytics, optimisation and compression
  • static code analysis & security scans

Pipeline Description and Requirements

System Requirements

This setup is designed for MacOS. The following tools must be installed and properly configured:

  • Docker
  • Git
  • Android SDK
  • XcodeandXcode Command Line Tools
  • cfgutil(included with Apple Configurator 2)

Note: adb and cfgutil are in system PATH.

React Native Expo Project

The React Native project must be configured to use EAS builds.

Place or clone it into ./react-native-project/.

Build Steps

Below is a simplified breakdown of the steps performed by this pipeline:

  1. Checkout react native project repository
  2. Install npm packages
  3. Run expo prebuild
  4. Setup credentials
  5. Build for Android
  6. Build for iOS
  7. Build for Web
  8. Run .apk on all connected Android devices
  9. Run .ipa on all connected iOS devices
  10. Launch docker container with web build

Credentials

You must prepare the necessary credentials for the EAS build system. Detailed instructions can be found in the Expo documentation: https://docs.expo.dev/app-signing/local-credentials/

Place the credentials file at ./credentials/credentials.preview.json.

Note: On MacOS 15+ you can open Keychain Access using terminal: open -a “Keychain Access”.

Note: iOS credentials must include an Ad Hoc provisioning profile and a Distribution certificate.

Secrets and Keys

In this example, I am not injecting secrets or keys directly into the build process. However, this can be done easily by introducing environment variables.

Let’s take the react-native-maps package and Google Maps API keys as an example:

  • On iOS, the API key is baked into the code during the prebuild process and must be available in the app configuration.
  • On Android, the API key is embedded into the AndroidManifest.xml file.

A possible approach:

  1. Switch the project to use a dynamic app.config.ts.
  2. Define API keys as environment variables in your build scripts (e.g., export GOOGLE_MAPS_API_KEY_ANDROID=...).
  3. In app.config.ts, read these API keys from the environment and inject them dynamically into the app configuration.
  4. For Android, implement a custom configuration plugin that uses the environment variable and injects it into the manifest (with AndroidConfig.Manifest.addMetaDataItemToMainApplication).

Build Script

In this example I’m using the simples possible approach for automation – shell script that works like build pipeline executing required steps one by one.

The script should be fairly self-explanatory. build.preview.sh:

#!/bin/bash

# Switch to the script folder
cd "$(dirname "$0")"

export EXPO_NO_GIT_STATUS=1

# Paths
PROJECT_DIR="react-native-project"
APK_PATH="../builds/android.apk"
IPA_PATH="../builds/ios.ipa"
WEB_PATH="../builds/web"

# Git configuration
USE_GIT="true"
GIT_BRANCH="development"

# Switch to the project dir
cd "$PROJECT_DIR"

# Git checkout
if [ "$USE_GIT" == "true" ]; then
    git restore . --staged --worktree
    git pull
    git checkout $GIT_BRANCH
fi

# Clean
rm -rf android/*
rm -rf ios/*
rm -rf dist/*
rm -rf ../builds/*

# Create buids/web dir
mkdir $WEB_PATH

# Install Node packages and prebuild android/ios projects
npm install
npx expo prebuild --clean

# Copy credentials
cp ../credentials/credentials.preview.json ./credentials.json

# Build Android
eas build --platform android --local --profile preview --output "$APK_PATH"

# Build iOS
eas build --platform ios --local --profile preview --output "$IPA_PATH"

# Build Web
npx expo export -p web

# Move web build to buids/web
mv dist/* "$WEB_PATH"

# Remove changes
if [ "$USE_GIT" == "true" ]; then
    git restore . --staged --worktree
fi

# Install .apk on all connected Android devices
adb devices | grep "device$" | while read -r line; do
    DEVICE_ID=$(echo "$line" | awk '{print $1}')
    if [ "$DEVICE_ID" != "List" ]; then
        adb -s "$DEVICE_ID" install "$APK_PATH"
    fi
done

# Install .ipa on all connected iOS devices
cfgutil --foreach install-app "$IPA_PATH"

# Launch docker container with web build
sh ../docker/Up.sh

What’s Next

This example can be extended into a full CI/CD pipeline. Some possible directions include:

  • CI/CD Integration
    Parts of the build script can be reused in pipelines powered by Jenkins, Local GitLab CI/CD, or other automation systems.
  • Testing & Quality Assurance
    A production-ready pipeline should integrate automated testing at multiple levels (unit, integration, end-to-end). It should also include static code analysis, linting, and other quality checks as part of the build process.
  • Secure Secrets Management
    The pipeline should be the only place where production credentials, signing certificates, and API keys are injected/used. Secrets must never be stored in repositories or on developer machines.
  • Automated Distribution
    • iOS: Automate .ipa distribution via OTA with website and email or Slack notifications, or push builds directly to TestFlight.
    • Android: Upload .apk/.aab files to Google Play Internal Testing tracks.
    • Web: Push the Dockerized web build to beta servers for browser-based testing.