Shipping to the app stores

How to set up CI/CD for a mobile app

Set up mobile CI/CD in five stages, from signed builds to store submission, and see how Bitrise, Codemagic, EAS and GitHub Actions handle code signing.

12 min read

Quick answer

A mobile CI/CD pipeline turns each merge into signed iOS and Android builds and moves them to testers, the stores and users. It has five stages: build signed binaries, run tests on simulators or devices, distribute builds to testers, submit to the stores, and ship fixes after release. Code signing and macOS build capacity are the hard parts, and each CI option answers them differently.

Why does a mobile app need CI/CD?

A one-line hotfix sat for four days last month because the only Mac that can make a release was on vacation with its owner. Her laptop holds the distribution certificate, the Android keystore and a shell script nobody else has run. Releases happen when she is in the office. When her laptop dies, you spend a week in the Apple Developer portal revoking certificates and asking Google to reset the upload key.

Most teams that search for "mobile CI/CD" start from that laptop. The goal is that any merge to main produces installable, signed builds, and that shipping a release does not depend on one person's hardware.

By the end of this guide you will know the five stages every mobile pipeline needs, what code signing requires on each platform, and how Bitrise, Codemagic, EAS Workflows and GitHub Actions handle it. You will also see a complete reference pipeline you can copy.

What are the five stages of a mobile CI/CD pipeline?

Web CI ends at a server you control. Mobile CI has five stages, and they run on different machines.

1. Build signed binaries. An Android App Bundle (.aab) or APK, and an iOS .ipa. iOS builds need macOS and Xcode. Android builds run on Linux. Both need signing credentials at build time.

2. Test. Unit tests run anywhere. End-to-end tests need an Android emulator or iOS Simulator, which means a Linux machine with nested virtualization for Android and a Mac for iOS.

3. Distribute to testers. Get the build onto a QA phone or a stakeholder's device before the stores are involved. On iOS this means TestFlight, ad hoc provisioning or enterprise distribution. On Android it means an APK link or a Play testing track.

4. Submit to the stores. Upload the .aab to Google Play Console and the .ipa to App Store Connect. Apple processes the upload before it appears in TestFlight, which the EAS Submit overview puts at 10 to 15 minutes in most cases. Store review is a separate, later step.

5. Ship fixes after release. A native release goes back through review. For React Native apps, an over-the-air update to the JavaScript bundle can reach users without a new binary, within the limits of App Store Review Guideline 2.5.2. Native code never changes this way and store review is not bypassed.

Most teams automate stage 1 first, then 4, then add 2 and 3 once builds are reliable. Stage 5 only applies if your app runs interpreted code.

What does code signing need on Android and iOS?

Signing is where first-time pipeline builders lose the most hours. The credentials differ per platform, and each CI option stores them differently.

What Android needs

A keystore file with a private key. Google requires every Android app to be signed before install. Since Google introduced Play App Signing, most apps sign with an upload key and Google re-signs with the app signing key it holds. If you lose the upload keystore you can ask Google to reset it. If you were still using the older model and lose the app signing key, the Expo app credentials guide is clear that there is no reset.

Never commit the keystore to the repository. Debug keystores are the one exception.

What iOS needs

Three things, all tied to a paid Apple Developer Program membership at $99 per year:

  • A distribution certificate. Tied to you, not to an app, and used for every app on the account. Expo's credentials reference lists a limit of two per account.
  • A provisioning profile, one per app. It expires after 12 months. An expired profile does not affect apps already in the store; you make a new one on the next build.
  • An App Store Connect API key, so a machine can talk to Apple without your Apple ID and two-factor prompt.

For test distribution there is a fourth: an ad hoc provisioning profile with an allow-list of device UDIDs. Apple caps ad hoc distribution at 100 iPhones per account per year, and adding a device means re-signing or rebuilding, as Expo's internal distribution guide explains.

What each CI option does with the credentials

OptionWhere credentials liveWhat you maintain
Bitrise"Managed code signing" for iOS and Android, kept out of source controlUploading renewed certificates and profiles
Codemagic"Built-in code signing identities management" with an Apple Developer portal integrationAn App Store Connect API key; renewals through the integration
EAS Build / EAS WorkflowsStored on EAS servers after eas credentials:configure-buildNothing per build; EAS generates and renews profiles. Local credentials are an option
GitHub Actions (plain)Encrypted repository secrets you upload as base64The import scripts, keychain setup and profile install steps in YAML
GitHub Actions + fastlane matchA private Git repo, Google Cloud Storage or S3 bucket that match encryptsThe match repo, its passphrase, and renewal when profiles expire

With EAS, a CI job needs one secret, EXPO_TOKEN, to trigger builds from CI. Repairing an iOS profile in a non-interactive job needs an App Store Connect API key passed through environment variables. Bitrise and Codemagic both let you upload credentials once and reference them from steps. The plain GitHub Actions route works, but you write and own every line of the keychain dance.

Where should you run mobile CI?

Three shapes exist, and plenty of teams combine them. Pick by how much of the pipeline is mobile-specific.

A general CI service with macOS runners. CircleCI, GitHub Actions, GitLab CI. You already have one. macOS minutes cost more than Linux minutes: GitHub's runner pricing is $0.062 per minute for a 3 or 4 core macOS runner against $0.006 for a 2 core Linux runner, and larger macOS runners run $0.077 to $0.102 per minute. Included free minutes (2,000 per month on GitHub Free, 3,000 on Team) cannot be used for larger runners. A general CI wins when your mobile app is one project among many and the team already lives in one CI system.

A mobile-specific CI service. Bitrise and Codemagic sell macOS capacity plus prebuilt steps for signing, building and store upload. Bitrise has a free Hobby plan with 300 credits a month. Its Starter plan is $99 a month billed monthly or $89 a month billed annually, and machine time is billed per minute on top.

Codemagic offers individuals 500 free minutes a month on macOS M2 and charges $0.095 per M2 minute or $0.114 per M4 minute after that, with $0.045 for Linux and Windows. Its fixed M2 plan is $3,990 a year for three concurrent builds and unlimited minutes, according to Codemagic's pricing docs. Both Bitrise and Codemagic list native iOS, native Android, Flutter, React Native, Ionic and Unity as supported. They win when the app is not React Native, or when you want Xcode and simulator steps ready-made without adopting an app platform.

An app-platform pipeline. EAS Workflows runs jobs on EAS-hosted macOS and Linux workers with job types that already know what a build, submit, update or Maestro run is. The Workflows introduction scopes it to Expo and React Native projects and says it does not fit pipelines that depend on Docker or custom runners. It wins when the app is React Native and you want one YAML file that covers all five stages.

Both. The common pattern is to keep unit tests, linting and backend jobs in GitHub Actions and hand the mobile stages to a mobile service. Expo's guide to integrating EAS Workflows with GitHub Actions shows an Actions job calling npx eas-cli workflow:run with --wait --json so it gets a machine-readable result.

How do Bitrise, Codemagic, EAS Workflows and GitHub Actions compare?

BitriseCodemagicEAS WorkflowsGitHub Actions
macOS runnersYes. M4 Pro with 54 GB RAM listed on its homepageYes. M2, M4, M4 MaxYes. macos-medium (5 cores, 20 GB) and macos-large (10 cores, 40 GB)Yes. 3 to 4 core M1 or Intel standard, 12 core and M2 Pro large
Code signingUpload files; steps install themUpload files or connect App Store ConnectManaged on EAS; one EXPO_TOKEN in CIYou script it, or add fastlane match
Mobile-specific steps"400+ pre-built steps"Code signing and upload automation for Flutter, React Native, native iOS and Android, Ionic, UnityJob types: build, submit, testflight, update, maestro, fingerprint, repackCommunity actions, including expo/expo-github-action
Pricing modelSubscription plus credits; free Hobby tierPer minute; 500 free macOS minutes; fixed annual plansFree tier (15 Android + 15 iOS builds, 60 Workflows minutes), then $19 or $199 per month plus usagePer minute by runner type; free minutes per plan
Best forNative and cross-platform teams wanting a large step library and new Xcode within 24 hours of releaseFixed-price unlimited minutes; 500 free macOS minutes to startReact Native teams wanting one pipeline for all five stagesTeams whose other CI already runs on GitHub

Sources: Bitrise pricing and bitrise.io, Codemagic pricing and codemagic.io, Expo pricing, GitHub runner pricing. Prices rechecked on each vendor's page on 8 October 2026.

What does a complete mobile pipeline look like?

The production release pipeline below is an EAS Workflow copied from the deploy to production example in the Expo docs. On every push to main it fingerprints the native code, checks whether a build with that fingerprint already exists, and either builds and submits new binaries or publishes a JavaScript update to the existing ones.

The deploy workflow covers stages 1, 4 and 5. For stage 2, add a maestro job that takes a simulator build ID and a flow path; the E2E example shows the build profile (withoutCredentials: true, ios.simulator: true, android.buildType: apk) and the two-job workflow. Maestro jobs are in alpha. For stage 3, the testflight job in the pre-packaged jobs reference adds a build to internal and external groups and can submit for Beta App Review.

The fingerprint step is the part worth copying even if you use another CI. In Expo's measurements of its own pipelines, an average full native build takes about 23 minutes, and repacking new JavaScript into an existing binary takes about 5 minutes. Most pull requests change only JavaScript, so skipping the native build on those is the single biggest time saving in a React Native pipeline.

How to trigger EAS from GitHub Actions

If you want the trigger in GitHub Actions and the mobile work elsewhere, the Expo guide to building on CI uses this shape:

The ubuntu-latest runner is the point. The iOS build happens on EAS's macOS workers, so you pay Linux minutes on GitHub and are not billed for Actions time while the build runs. Drop --no-wait if a later step needs the finished build.

Where Expo fits

EAS Workflows is Expo's CI/CD service for Expo and React Native apps. It runs the five stages as pre-packaged job types on EAS-hosted workers: build, submit, testflight, update, update-rollout, maestro, fingerprint, get-build and repack, plus custom jobs that run shell commands. The Workflows introduction lists the triggers: GitHub push, pull request and label events, cron schedules, App Store Connect events, the CLI, or the REST API.

Getting the first workflow running is two commands after npm install -g eas-cli:

The deploy template writes the production pipeline shown above and configures EAS Build and EAS Update for the project, as the get started guide shows. Production builds need signing credentials, set up once per platform:

Staged rollouts are built in. An update job accepts rollout_percentage, a require-approval job pauses the workflow for a human, and an update-rollout job raises the percentage to 100 after approval. The pre-packaged jobs reference documents each one.

Limitations

EAS Workflows is for Expo and React Native projects configured for EAS Build. The Workflows introduction lists "highly customized pipelines that depend on non-EAS services (Docker, custom runners)" as a poor fit, and the limitations page confirms there are no shared workflow configurations and no matrix builds. The fingerprint job only works with Continuous Native Generation; if you commit android and ios directories it will not run. Maestro jobs are alpha.

On cost, the Free plan includes up to 15 Android and 15 iOS builds and 60 Workflows minutes. Starter is $19 a month with $45 of credit and Production is $199 a month with $225 of credit, both usage-based beyond that. Large workers need Starter or above, per Expo's pricing page.

Key numbers for mobile CI/CD

FactValueSource
Apple Developer Program$99 per yearApple
iOS provisioning profile validity12 monthsExpo app credentials
Ad hoc device limit100 iPhones per account per yearExpo internal distribution
TestFlight processing after uploadUsually 10 to 15 minutesEAS Submit overview
GitHub macOS runner$0.062 per minute (3 or 4 core)GitHub
Codemagic macOS M2$0.095 per minute, 500 free minutes a month for individualsCodemagic
EAS macOS workersmacos-medium 5 cores, 20 GB; macos-large 10 cores, 40 GBWorkflows introduction
Full native build vs repackAbout 23 minutes vs about 5 minutes (Expo's pipelines)Expo blog

Next step

Automate stage 1 this week. If your app is React Native, run eas workflow:create --template build and push once; the Workflows get started guide covers the prompts. If your app is native Swift or Kotlin, start with a single macOS job that builds and archives, and add signing with fastlane match before anything else.

Verified on 12 September 2026.

Keep reading

Frequently Asked Questions