Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Build React Native iOS in CI on a Mac Runner

React Native iOS CI wants an Apple silicon Mac so Metro, Hermes, and the iOS simulator all run arm64 natively, and so the archive step uses a current Xcode. Request a Figment M-series Mac from $95/month (exact chip, RAM, and disk are confirmed in the quote) at /mac-hosting/start and register it as a GitHub self-hosted runner. If you want the CI setup, backups, monitoring, and 30 minutes of monthly support handled for you, step up to Managed Build Mac at $249/month (one M-series Mac, CI setup, backups, monitoring, 30 minutes monthly support).

Figment also offers a $29 virtual Intel macOS VM (2 vCPU, 8 GiB RAM, 80 GiB disk, browser-console access, no GPU acceleration). It is real macOS and can host a compatible Intel toolchain — per Apple's porting guide, "if you need to run Xcode on an Intel-based processor, use Xcode 26 or earlier" (developer.apple.com). For React Native iOS release work in 2026 that is usually too constrained: Xcode 27 and arm64-native builds require Apple silicon. Use the $29 VM for scripting, dependency caches, or Intel toolchain validation; use the M-series Mac for the signed TestFlight pipeline below.

The React Native iOS Build Chain

A React Native iOS build wires together three ecosystems in a fixed order: Node (Metro, Hermes), Ruby + CocoaPods (native pods declared by npm packages), then Xcode (archive and sign). Skip or reorder any step and the error surfaces in the next layer — a missing Node yields a Metro error, a skipped pod install yields an Xcode "module not found," and a missing scheme yields an xcodebuild exit code. Apple's xcodebuild technote TN2339 documents the -workspace, -scheme, and -archivePath arguments the workflow below uses; React Native's Running on device guide covers the iOS workspace and signing prerequisites.

Pinning Node and Ruby for Reproducible Runs

Toolchain drift is the biggest source of flaky iOS CI. Pin both runtimes explicitly:

  • Node: actions/setup-node with a specific major (Node 20 LTS is a safe default; defer to package.json engines when it exists). cache: 'npm' caches the npm download index keyed on package-lock.json; npm ci still populates node_modules.
  • Ruby: ruby/setup-ruby with bundler-cache: true. This caches vendor/bundle keyed on Gemfile.lock, which should pin both CocoaPods and Fastlane. Avoid sudo gem install — the bundler cache is faster and reproducible.

Cache node_modules and ios/Pods with separate keys so an npm change does not invalidate your Pods cache. Key the Pods cache on ios/Podfile.lock, which is the precise record of resolved Pod versions.

Scheme, Workspace, and the Archive Step

CocoaPods produces ios/YourApp.xcworkspace; always build the workspace, never the bare .xcodeproj. Find the real scheme on your own machine first — do not guess it in CI:

xcodebuild -list -workspace ios/YourApp.xcworkspace

Copy the exact scheme into Fastlane's build_app call. Use Fastlane Match with --readonly in CI so a PR build never tries to create new certificates (which would fail without interactive auth and can disturb your signing pool). Match stores encrypted certificates and provisioning profiles in a private git repo; the CI runner pulls them under a temporary keychain Match manages for the job. Keep the .p8 App Store Connect key, the Match encryption password, and the Match git token in your CI secret manager — never check them into the repo and never dump the shell environment to a file.

The GitHub Actions Workflow and Fastfile

Replace YourApp with your actual scheme. Register your Figment M-series Mac as a self-hosted runner and label it mac-arm64; adjust runs-on to match.

# .github/workflows/ios-ci.yml
name: React Native iOS CI

on:
  push:
    branches: [main, 'release/**']
  pull_request:
    branches: [main]

jobs:
  build-ios:
    runs-on: [self-hosted, mac-arm64]
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci --prefer-offline

      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.3'
          bundler-cache: true

      - uses: actions/cache@v4
        with:
          path: ios/Pods
          key: pods-${{ runner.os }}-${{ hashFiles('ios/Podfile.lock') }}
          restore-keys: pods-${{ runner.os }}-

      - name: Install CocoaPods
        working-directory: ios
        run: bundle exec pod install

      - name: Sync signing material (read-only)
        run: bundle exec fastlane match appstore --readonly
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_BASIC_AUTHORIZATION }}

      - name: Archive and upload to TestFlight
        run: bundle exec fastlane beta
        env:
          APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.ASC_KEY_ID }}
          APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
          APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.ASC_PRIVATE_KEY }}

The matching fastlane/Fastfile (see the Fastlane iOS beta deployment guide for the primary reference):

default_platform(:ios)

platform :ios do
  lane :beta do
    app_store_connect_api_key(
      key_id:                ENV.fetch("APP_STORE_CONNECT_API_KEY_ID"),
      issuer_id:             ENV.fetch("APP_STORE_CONNECT_API_ISSUER_ID"),
      key_content:           ENV.fetch("APP_STORE_CONNECT_API_KEY_CONTENT"),
      is_key_content_base64: true
    )

    build_app(
      workspace:     "ios/YourApp.xcworkspace",
      scheme:        "YourApp",
      configuration: "Release",
      export_method: "app-store"
    )

    upload_to_testflight(skip_waiting_for_build_processing: true)
  end
end

Steps return non-zero on failure and GitHub Actions stops the job automatically — a failed archive is a failed run, never a "fast" one. Run the workflow once on a feature branch so you read the first log without release-week pressure. First runs populate the npm and CocoaPods caches; subsequent runs reuse them.

Provision these five repository secrets before the first run:

SecretContents
MATCH_PASSWORDEncryption password for the Match git repo
MATCH_GIT_BASIC_AUTHORIZATIONBase64 user:token for Match git access
ASC_KEY_IDApp Store Connect API key ID
ASC_ISSUER_IDApp Store Connect issuer UUID
ASC_PRIVATE_KEYBase64-encoded .p8 key content

Picking a Figment Plan and a Worked Cost Check

Three Figment options are relevant to this workflow:

PlanPriceWhat you get
Virtual Intel macOS$29/mo2 vCPU, 8 GiB, 80 GiB, browser console. Good for Intel-toolchain scripts and caches; not for arm64 or Xcode 27.
M-series Macfrom $95/moPhysical Apple silicon Mac; exact chip, RAM, and disk confirmed in the quote. Request-based while supply is confirmed.
Managed Build Mac$249/moOne M-series Mac, CI setup, backups, monitoring, 30 minutes monthly support.

Honest price comparison against the obvious alternative for a single arm64 React Native runner. CloudMac lists an M4 16 GB / 256 GB at $99.99/month on cloudmac.org/pricing and a $999/year option on cloudmac.org. That is an effective $83.25/month on the annual term — $141 less per year than 12 × $95 = $1,140 if the Figment quote lands at $95. CloudMac's $99.99 monthly rate is also a published M4/16/256 configuration; Figment's $95 is an unspecified M-series entry point, so only compare entry price, not hardware. Figment's differentiator for this page is the Managed Build Mac plan: CloudMac is a hardware lease, Managed Build Mac adds the CI setup, backups, monitoring, and support minutes against your pipeline. If you only need bare hardware and the lowest annual outlay, CloudMac's annual term wins on price; if you want the CI work delivered, request a Figment quote.

Verify arm64 once the runner is provisioned:

uname -m                                # expect arm64
node -e "console.log(process.arch)"     # expect 'arm64'
xcodebuild -version

Request a configuration quote at /mac-hosting and start onboarding at /mac-hosting/start.

Related guides: Build Flutter iOS in CI on a Mac runner · Send Slack alerts for iOS CI build status · Remote Xcode setup on Windows


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms