Don't Pay for EAS! How to Set Up an EAS Local Build on GitHub Actions
Tags: expo, reactnative, eas, githubactions
Published: 2024-02-14 21:56:24

You're out of credits!

_Should you pay for EAS?_

If you're a React Native developer using Expo, you might be familiar with the convenience of Expo Application Services (EAS). EAS provides a suite of tools that streamline the process of developing and deploying your React Native apps. 

One drawback of EAS is the price, which at $1-$4 per build may quickly add up for a developer just getting started on a project.

Luckily, one of the great features of the EAS CLI is the ability to perform local builds, and integrating this with GitHub Actions can significantly automate your development workflow and save you money!

In this post, we'll walk through the steps to set up an EAS local build on GitHub Actions.

![EAS CLI + Github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uuau25zy6tzzzrzanwwm.jpg)

## Pricing & Build Times
It's important to compare prices and build times, to see when this implementation makes sense.

Let's start with GitHub Actions. If you're trying to optimize for cost, you're likely on the free GitHub plan or the basic $4 Github Teams/Pro plan. You're going to need to run iOS builds on a macOS while Ubuntu should work just fine for Android.

While build times vary, for simplicity, an average build takes about 35 minutes. At **$0.008 PPM** (price per minute) for Ubuntu 2-core and **$0.08 PPM** for macOS 3-core, that comes out to **$0.28 per build for Android** and **$2.80 for iOS.** That's an expensive iOS build.

Now let's look at EAS pricing. Depending on your choice of resources, your potential EAS build will use more cores and thus run a lot faster than on GitHub Actions. That may be worth paying for depending on your needs.

![EAS Pricing](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p25a2o0e4b3n41ig59sl.png)

As you can see above, however, builds are pricy on EAS as well. That said, if build time is not a priority, you can find big savings on the Android side by going with GitHub Actions given the low cost of running Ubuntu.

Thus, your choice between EAS or GitHub Actions depends largely on a) how fast you need the build to run, and b) the frequency of your builds since running lots of builds will quickly add up.

For iOS builds, I think EAS comes out slightly ahead at a flat $2 per build if you go with the iOS medium worker size.

## Setup
### Step 1: Understanding the Prerequisites

Before diving into the setup, ensure you have:
- A React Native project managed by Expo.
- A GitHub repository for your project.
- Basic knowledge of GitHub Actions and workflows.

### Step 2: Setting Up Your GitHub Actions Workflow

**Create a Workflow File**:
In your project repository, create a new file in the `.github/workflows` directory. You can name it something like `build.yml`.

```yaml
name: Build App
on:
  workflow_dispatch:
    inputs:
      os:
        type: choice
        description: OS to build on. Ubuntu is faster, MacOS supports iOS builds
        options:
          - macos-latest
          - ubuntu-latest
      platform:
        type: choice
        description: Platform to build for
        options:
          - android
          - ios
      profile:
        type: choice
        description: Build profile to use
        options:
          - development
          - preview
          - production
      should_submit:
        type: boolean
        description: Whether to perform the submit step
        required: true
        default: false
jobs:
  build:
    runs-on: ${{ github.event.inputs.os }}
    strategy:
      matrix:
        node: [18.x]
    steps:
    - name: 🏗 Setup repo
      uses: actions/checkout@v2

    - name: 🏗 Setup Node
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node }}
        cache: yarn

    - name: 🏗 Setup Expo and EAS
      uses: expo/expo-github-action@v7
      with:
        token: ${{ secrets.EXPO_TOKEN }}
        expo-version: latest
        eas-version: latest

    - name: 📦 Install dependencies
      run: yarn

    - name: 📋 Test project
      run: yarn test

    - name: 👷 Build app
      run: |
        eas build --local \
          --non-interactive \
          --output=./app-build \
          --platform=${{ github.event.inputs.platform }} \
          --profile=${{ github.event.inputs.profile }}

    - name: 🚢 Submit
      if: ${{ github.event.inputs.should_submit }}
      run: eas submit -p ${{ github.event.inputs.platform }} --profile ${{ github.event.inputs.profile }} --path app-build

```

**Add Required Secrets**:
For EAS to work, you need to add your Expo credentials as secrets in your GitHub repository. Go to your repository Settings -> Secrets and variables -> Actions. This is where you will paste in your [access token](https://docs.expo.dev/accounts/programmatic-access/). Double-check that you are creating the robot token at the right level (e.g. wherever your app is).

![Github Secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aakw62k2o322xjv97zy0.png)

Finally, add any other required secrets you may need. A common example is the Google Services files.

**Google Services Files**
If you are not using Google Services files, skip ahead.

Problem: consider the scenario where your project needs these files, but you don't want to commit them. If that's the case, Github Actions wouldn't have access to their contents.

Solution: base64-encode the files, add them as secrets to Github, and then use a [pre-install hook](https://docs.expo.dev/build-reference/npm-hooks/) to re-create the files in Github Actions' cloud environment.

1. `$ base64 -i googleServices/google-services.json` (repeat for Plist, other files)
2. Add encoded strings as secrets to Github Actions
3. Write a script to decode them into their original files:
```sh
#!/bin/bash

# Create the directory if it doesn't exist
mkdir -p ./googleServices

# Decode the base64 strings and create the files
echo $GOOGLE_SERVICES_JSON_BASE64 | base64 --decode > ./googleServices/google-services.json
echo $GOOGLE_SERVICES_PLIST_BASE64 | base64 --decode > ./googleServices/GoogleService-Info.plist
```
4. Call that script from the `eas-build-pre-install` hook in your package.json.
5. Make the variables accessible via our build YML:

```yml
- name: 👷 Build app
      env:
        GOOGLE_SERVICES_JSON_BASE64: ${{ secrets.GOOGLE_SERVICES_JSON_BASE64 }}
        GOOGLE_SERVICES_PLIST_BASE64: ${{ secrets.GOOGLE_SERVICES_PLIST_BASE64 }}
        GOOGLE_SERVICES_JSON: ${{ vars.GOOGLE_SERVICES_JSON }}
        GOOGLE_SERVICES_PLIST: ${{ vars.GOOGLE_SERVICES_PLIST }}
```

Note that in this example, I am using `GOOGLE_SERVICES_JSON` and `GOOGLE_SERVICES_PLIST` as well. These contain the paths to the files.

![Github Variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c027myg9n0kn79wz60eo.png)

This is applicable if you don't have a hard-coded path in your app.json / app.config.js:

```
  ios: {
    googleServicesFile: process.env.GOOGLE_SERVICES_PLIST,
  }
```

### Step 3: Testing the Workflow

Once you've set up the workflow file:
1. **Commit and Push**: Commit the changes and push them to your repository.
2. Trigger the build via the Github Actions tab.
2. Click on the build to monitor it. 

### Step 4: Debugging and Optimization

- If the build fails, check the logs in the Actions tab for errors.
- Optimize your workflow by caching dependencies or splitting jobs.
- If you know your tests pass, exclude that step and others to iterate more quickly until your build is stable.

### Conclusion

Integrating EAS local builds into GitHub Actions can greatly enhance your CI/CD pipeline and save you some of the green. Remember, this setup might vary slightly depending on your project's specific needs, so feel free to adjust it as necessary.

And finally please feed the algorithm and give me some feedback if my post helped you 🙏!

## Follow Me
<div id="badges">
  <a href="https://www.linkedin.com/in/rogomezpalacio/">
    <img src="https://img.shields.io/badge/LinkedIn-blue?style=for-the-badge&logo=linkedin&logoColor=white" alt="LinkedIn Badge"/>
  </a>
  <a href="https://twitter.com/ro_gmzp">
    <img src="https://img.shields.io/badge/Twitter-blue?style=for-the-badge&logo=twitter&logoColor=white" alt="Twitter Badge"/>
  </a>
</div>

Check out my other articles and projects!
* [Expo Pod Pinner](https://dev.to/rgomezp/introducing-expo-pod-pinner-elevate-your-expo-managed-workflow-with-precision-4k67)
* [Perplexity SDK](https://dev.to/rgomezp/introducing-the-unofficial-perplexity-sdk-for-nodejs-52kp)