A Practical Guide to Running Expo EAS Builds on Windows
If you’re using Expo EAS for React Native builds, you know that cloud builds can get expensive quickly. But what if you could run EAS builds locally on GitHub Actions and test them before pushing? This post introduces a complete, end-to-end automation pipeline for building Expo apps locally using eas build --local, all orchestrated with GitHub Actions, enhanced with Bash shell scripting, and even PowerShell scripts for Windows developers. The goal? Local development speed, full control, and $0 spent on EAS remote builds unless absolutely necessary. What This Setup Does This configuration automates: Sets up an environment with all required dependencies (Node, Java, Android SDK, etc.) Performs a local Expo build via GitHub Actions and eas build --local Handles GitHub CLI installation and usage Dynamically generates Android keystores and credentials Uploads APK artifacts to GitHub Releases Powers full local build execution using act (the GitHub Actions emulator) Includes PowerShell scripts to run the entire CI/CD workflow locally on Windows It’s a self-contained pipeline that works both in GitHub Actions and in your local terminal. Why Go Local? Using GitHub Actions with local builds can bring down costs. You’ll still get automation, artifact uploading, and continuous delivery, without paying EAS for each build. Bonus: With act and PowerShell, you can run the entire pipeline offline, fully replicating GitHub’s CI/CD in your development machine. Prerequisites Before diving into the setup of the GitHub Actions workflow and local build scripts, ensure the following: A React Native Project Managed by Expo: You need an existing React Native project that uses Expo for easy app building and management. Make sure the project is correctly configured with the necessary Expo and EAS dependencies. GitHub Repository: Ensure your project is hosted on GitHub. You’ll need to push your code to a GitHub repository to leverage GitHub Actions for CI/CD. Basic Understanding of GitHub Actions: Familiarity with how GitHub Actions work, including setting up workflows and actions in the .github/workflows/ directory. You don’t need to be an expert, but understanding the basics will help you troubleshoot and customize the workflow. Access to Expo and GitHub Tokens: You’ll need Expo tokens for authenticating with the Expo API and GitHub tokens for accessing your repository and uploading releases. Be sure to create and configure these tokens properly in the GitHub Secrets section of your repository settings. Local Environment Setup: You should have PowerShell installed on Windows, which will allow you to run the PowerShell scripts locally. The scripts are used to simulate and test the GitHub Actions workflow locally. ACT and Docker: To simulate GitHub Actions runs on your local machine, you’ll need ACT — a command-line tool that runs GitHub Actions workflows locally using Docker. This is crucial for testing and debugging before pushing the changes to GitHub. Make sure both ACT and Docker are installed and configured properly on your system. Once you’ve completed these steps, you’re ready to proceed with setting up the GitHub Actions workflow and local build scripts. Now, let’s go over how the automation works and the code structure. The GitHub Actions Workflow Here’s the core build.yaml workflow: name: Expo on: workflow_dispatch: inputs: os: type: string default: ubuntu-latest platform: type: string default: android profile: type: string default: preview jobs: job: runs-on: ${{ github.event.inputs.os }} timeout-minutes: 30 steps: - name: Setup repo uses: actions/checkout@v4 - name: Setup Act and Env run: | source "./tools.sh" runner "apt-get update >/dev/null 2>&1" runner "apt-get install unzip -y >/dev/null 2>&1" unzip -v runner "apt-get install git -y >/dev/null 2>&1" git --version runner "$(declare -f 'gh_try'); gh_try" gh --version - name: Setup Node uses: actions/setup-node@v2 with: node-version: '18.x' - name: Setup Java uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' - name: Setup Android SDK uses: android-actions/setup-android@v3 - name: Test Environment env: GH_TOKEN: ${{ secrets.TOKEN }} run: | adb --version node --version java --version gh repo view --json name,url - name: Install Dependencies run: | yarn --silent > /dev/null 2>&1 - name: Run Dummy Tests run: | yarn add --dev jest --silent > /dev/null 2>&1 yarn test - name: Setup Expo uses: expo/expo-github-action@v7 with: toke

If you’re using Expo EAS for React Native builds, you know that cloud builds can get expensive quickly. But what if you could run EAS builds locally on GitHub Actions and test them before pushing?
This post introduces a complete, end-to-end automation pipeline for building Expo apps locally using eas build --local
, all orchestrated with GitHub Actions, enhanced with Bash shell scripting, and even PowerShell scripts for Windows developers. The goal? Local development speed, full control, and $0 spent on EAS remote builds unless absolutely necessary.
What This Setup Does
This configuration automates:
- Sets up an environment with all required dependencies (Node, Java, Android SDK, etc.)
- Performs a local Expo build via GitHub Actions and
eas build --local
- Handles GitHub CLI installation and usage
- Dynamically generates Android keystores and credentials
- Uploads APK artifacts to GitHub Releases
- Powers full local build execution using
act
(the GitHub Actions emulator) - Includes PowerShell scripts to run the entire CI/CD workflow locally on Windows
It’s a self-contained pipeline that works both in GitHub Actions and in your local terminal.
Why Go Local?
Using GitHub Actions with local builds can bring down costs. You’ll still get automation, artifact uploading, and continuous delivery, without paying EAS for each build.
Bonus: With act
and PowerShell, you can run the entire pipeline offline, fully replicating GitHub’s CI/CD in your development machine.
Prerequisites
Before diving into the setup of the GitHub Actions workflow and local build scripts, ensure the following:
- A React Native Project Managed by Expo: You need an existing React Native project that uses Expo for easy app building and management. Make sure the project is correctly configured with the necessary Expo and EAS dependencies.
- GitHub Repository: Ensure your project is hosted on GitHub. You’ll need to push your code to a GitHub repository to leverage GitHub Actions for CI/CD.
-
Basic Understanding of GitHub Actions: Familiarity with how GitHub Actions work, including setting up workflows and actions in the
.github/workflows/
directory. You don’t need to be an expert, but understanding the basics will help you troubleshoot and customize the workflow. - Access to Expo and GitHub Tokens: You’ll need Expo tokens for authenticating with the Expo API and GitHub tokens for accessing your repository and uploading releases. Be sure to create and configure these tokens properly in the GitHub Secrets section of your repository settings.
- Local Environment Setup: You should have PowerShell installed on Windows, which will allow you to run the PowerShell scripts locally. The scripts are used to simulate and test the GitHub Actions workflow locally.
- ACT and Docker: To simulate GitHub Actions runs on your local machine, you’ll need ACT — a command-line tool that runs GitHub Actions workflows locally using Docker. This is crucial for testing and debugging before pushing the changes to GitHub. Make sure both ACT and Docker are installed and configured properly on your system.
Once you’ve completed these steps, you’re ready to proceed with setting up the GitHub Actions workflow and local build scripts. Now, let’s go over how the automation works and the code structure.
The GitHub Actions Workflow
Here’s the core build.yaml
workflow:
name: Expo
on:
workflow_dispatch:
inputs:
os:
type: string
default: ubuntu-latest
platform:
type: string
default: android
profile:
type: string
default: preview
jobs:
job:
runs-on: ${{ github.event.inputs.os }}
timeout-minutes: 30
steps:
- name: Setup repo
uses: actions/checkout@v4
- name: Setup Act and Env
run: |
source "./tools.sh"
runner "apt-get update >/dev/null 2>&1"
runner "apt-get install unzip -y >/dev/null 2>&1"
unzip -v
runner "apt-get install git -y >/dev/null 2>&1"
git --version
runner "$(declare -f 'gh_try'); gh_try"
gh --version
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '18.x'
- name: Setup Java
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Test Environment
env:
GH_TOKEN: ${{ secrets.TOKEN }}
run: |
adb --version
node --version
java --version
gh repo view --json name,url
- name: Install Dependencies
run: |
yarn --silent > /dev/null 2>&1
- name: Run Dummy Tests
run: |
yarn add --dev jest --silent > /dev/null 2>&1
yarn test
- name: Setup Expo
uses: expo/expo-github-action@v7
with:
token: ${{ secrets.EXPO_TOKEN }}
eas-version: latest
expo-version: latest
- run: eas whoami
- name: EAS Init
run: |
eas init --non-interactive --force
[ ! -f eas.json ] && echo '{
"build": {
"preview": {
"developmentClient": false,
"distribution": "internal",
"android": {
"credentialsSource": "local",
"buildType": "apk"
}
}
}
}' > eas.json
- name: Create Keystore
run: |
source "./tools.sh"
key_create "com.spicytech.test2"
- name: EAS Local Build
run: |
EAS_NO_VCS=1 eas build --local \
--non-interactive \
--output=./app-build.apk \
--platform=${{ github.event.inputs.platform }} \
--profile=${{ github.event.inputs.profile }}
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: ArtifactName
path: app-build.apk
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.TOKEN }}
run: |
source "./tools.sh"
release_with_artifact '${{ github.repository }}' '${{ github.actor }}' "XYZ" app-build.apk
Helper Bash Functions (tools.sh)
The following functions power setup, fallback installs, keystore generation, and GitHub Release management:
runner() {
CMD="$*"
if command -v sudo >/dev/null 2>&1; then
sudo bash -c "$CMD"
else
bash -c "$CMD"
fi
}
gh_try() {
if ! command -v gh >/dev/null 2>&1; then
echo "Installing GitHub CLI..."
apt-get install -y gnupg curl
curl -fsSL https://cli.github.com/... | gpg --dearmor -o /usr/share/keyrings/...
echo "deb ..." | tee /etc/apt/sources.list.d/github-cli.list
apt-get update
apt-get install -y gh
else
gh auth status || true
gh repo view || true
fi
}
key_create() {
keytool -genkey -v -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 \
-storepass KEYSTORE_PASSWORD -keypass KEY_PASSWORD \
-alias KEY_ALIAS -keystore keystore.jks \
-dname "CN=${1},OU=,O=,L=,S=,C=US"
cat < credentials.json
{
"android": {
"keystore": {
"keystorePath": "keystore.jks",
"keystorePassword": "KEYSTORE_PASSWORD",
"keyAlias": "KEY_ALIAS",
"keyPassword": "KEY_PASSWORD"
}
}
}
EOF
}
release_with_artifact() {
repo=$1
actor=$2
tag_name=$3
artifact=$4
git config user.name "$actor"
git config user.email "$actor+$actor@users.noreply.github.com"
gh release delete "$tag_name" --repo "$repo" --yes 2>/dev/null || true
git fetch --tags
git push --delete origin "$tag_name" 2>/dev/null || true
git tag --delete "$tag_name" 2>/dev/null || true
gh release create "$tag_name" --repo "$repo" --title "Releasing ${tag_name}" --notes "Released at '$(date)' by '$actor'"
gh release upload "$tag_name" "$artifact" --repo "$repo"
}
PowerShell Script for Local Execution (Windows)
This is a PowerShell script to run the workflow locally using act
, simulating GitHub Actions:
function INSTALL_EXECUTE
{
$REPO = "https://:@github.com//.git"
$WORKFLOW = "build.yaml"
git.exe clone $REPO $FolderName
Set-Location $FolderName
act.exe `
--secret "TOKEN=$TOKEN" `
--secret "EXPO_TOKEN=$EXPO_TOKEN" `
--artifact-server-path $EXECUTION_PATH `
--platform windows-2019=-self-hosted `
--workflows .github\workflows\$WORKFLOW `
-e ..\event.json
Set-Location $EXECUTION_PATH
}
with
{
"event_name": "workflow_dispatch",
"inputs": {
"os": "ubuntu-latest",
"platform": "android",
"profile": "preview"
}
}
Conclusion
This setup gives you all the power of CI/CD, without the cost of cloud builds. It’s fully customizable, and especially great for solo devs and indie projects. The ability to run it all locally — on Linux via Bash or on Windows via PowerShell — makes it a game-changer.
If this helped you streamline your pipeline, give a star to the repo, share it with your team, and don’t forget to feed the GitHub algorithm.
Bon appétit!!