Skip to content

How it works ​

There is no server in the middle. The CLI talks to two things: the GitHub API, to run and read builds, and asc, to talk to App Store Connect.

rnd build  ──►  resolve the eas.json profile       (a port of @expo/eas-json)
           ──►  translate it into a build plan      (distribution → export method, …)
           ──►  workflow_dispatch                   (.github/workflows/eas-build.yml)
           ──►  poll the run; download the artifact

rnd submit ──►  resolve the submit profile
           ──►  asc builds upload                   ──►  App Store Connect
           ──►  asc builds add-groups               ──►  TestFlight

A build is a workflow run ​

rnd build dispatches eas-build.yml with the resolved plan as its input. The run is the build: its status, step timings, logs and artifacts are what rnd build:list, rnd build:view and the dashboard show.

To find its own run again, the CLI stamps a random dispatch id into the run name, together with the platform, profile, distribution and whether it is a simulator build:

ios preview build [eas:dispatch_id=1b0f302e-…;platform=ios;profile=preview;distribution=internal;simulator=false]

GitHub does not return dispatch inputs on a run, so this run name is how the platform, profile and distribution survive.

Build ids are a SHA-256 of owner/repo#runId, formatted as a v4-shaped UUID. They are stable, need nothing stored, and come out the same in Node and in the browser. Anywhere a build id is accepted, a unique prefix or the raw run id works too.

What the runner does ​

StepiOS (macos-26)Android (ubuntu-latest)
Planunpack the build plan into step outputssame
Environmentexport the profile's env before the app config is readsame
InstallNode and your package manager; install from your lockfilesame, plus JDK 17 and the Android SDK
Prebuildexpo prebuild unless ios/ / android/ are committedsame
Native depspod install, with a Pods cache—
Signimport the p12 into a throwaway keychain; apply the profile to the app targetinject a keystore through Gradle properties, if present
Buildxcodebuild archive + export, or a simulator buildthe Gradle task for the profile
Publishinternal builds → the install servicesame
Uploadapplication-archive artifact (90 days), plus Xcode logsapplication-archive artifact

The full list, and the secrets and variables each step reads, is in the workflow reference.

Failures are failures: if xcodebuild exits non-zero, or a simulator build produces no usable .app, the step fails and points you at the xcode-logs artifact. A build is never reported green with an empty artifact.

The dashboard shares the CLI's code ​

The dashboard imports the same module the CLI uses to turn a workflow run into an EAS build record. rnd build:list --json and the dashboard cannot disagree about a build's status, duration or artifact.

Cost ​

Runner minutes come out of your GitHub account.

RepositorymacOS (iOS builds)Linux (Android builds)
Publicfreefree
Private10× multiplier: a Free plan's 2,000 min/month is about 200 macOS minutes1×

A typical React Native iOS archive takes 6–15 minutes and Android 4–8. resourceClass: "large" maps to macos-26-xlarge, which is never covered by included minutes and bills per minute on every plan; the CLI warns before dispatching one.

Public repositories and artifacts

On a public repository, workflow artifacts (your .ipa and .apk) can be downloaded by any signed-in GitHub user for their 90-day retention. Repository secrets stay private.

Builds run on your GitHub Actions minutes; signing assets stay in your repo secrets.