Code Signing
Ready to sign and distribute? Set up Apple + Android signing for your app.
This guide walks you through setting up a RunnerHub pipeline for React Native projects that target both iOS and Android. Build, test, code sign, and deploy your app with automated dependency caching and unified Node.js management.
Migration Notice
Already using platform: ios for React Native? Switch to platform: react-native to get:
fnm install)Your existing platform: ios pipelines keep working — migration is opt-in.
Create .runnerhub/runnerhub.yml in your repository root with a multi-job pipeline for iOS and Android:
name: React Native Buildplatform: react-native
environment: node: lts xcode: "16.4" android_sdk: 34
triggers: - push - pull_request
jobs: test: name: Unit Tests steps: - name: Install dependencies run: npm install
- name: Run tests run: npm test
build-ios: name: Build iOS needs: [test] steps: - name: Install dependencies run: npm install
- name: Install pods run: | cd ios pod install
- name: Build iOS App run: | cd ios xcodebuild -workspace App.xcworkspace -scheme App build
build-android: name: Build Android needs: [test] steps: - name: Install dependencies run: npm install
- name: Build Android App run: | cd android ./gradlew assembleReleaseReplace App with your actual app name and adjust workspace/gradle paths as needed.
The environment block now includes:
node: lts — Automatically installs and activates the Node.js LTS version. Accepted values: specific versions like "20", "20.11", "20.11.1", or release channels "lts" and "latest"xcode: "16.4" — Required for iOS buildsandroid_sdk: 34 — Required for Android buildsruby: "3.2" — If using Bundler or CocoaPods with Ruby requirementsUpdate the YAML with your project specifics:
npm install to yarn install if needed. To use pnpm, add a setup step: npm install -g pnpm (pnpm is not pre-installed on golden images; see line 242 for a complete example)assembleRelease as needed (e.g., bundleRelease for App Bundle).runnerhub/runnerhub.yml to your repositoryHere’s a more comprehensive pipeline including linting, code signing for both platforms, and artifact uploads:
name: React Native Build & Signplatform: react-native
environment: node: "20.11" xcode: "16.4" android_sdk: 34
triggers: - push - pull_request
jobs: lint-and-test: name: Lint & Test steps: - name: Install dependencies run: npm install
- name: Lint run: npm run lint
- name: Run tests run: npm test
build-ios-signed: name: Build & Sign iOS needs: [lint-and-test] steps: - name: Install dependencies run: npm install
- name: Install pods run: | cd ios pod install
- name: Build iOS Release run: | cd ios xcodebuild \ -workspace App.xcworkspace \ -scheme App \ -configuration Release \ -derivedDataPath build \ build
artifacts: - ios/build/**/*.app - ios/build/**/*.dSYM
build-android-signed: name: Build & Sign Android needs: [lint-and-test] steps: - name: Install dependencies run: npm install
- name: Build Android Release run: | cd android ./gradlew bundleRelease
artifacts: - android/app/build/outputs/bundle/release/**/*.aabThis example demonstrates:
triggers to gate to the main branch20.11) for consistencyTo make the signed builds run only on main, update triggers to:
triggers: - event: push branches: [main] - event: pull_request branches: [main]If your project uses Yarn instead of npm:
name: React Native Buildplatform: react-native
environment: node: lts xcode: "16.4" android_sdk: 34
triggers: - push - pull_request
jobs: build-ios: name: Build iOS steps: - name: Install dependencies run: yarn install
- name: Install pods run: | cd ios pod install
- name: Build iOS run: | cd ios xcodebuild -workspace App.xcworkspace -scheme App build
build-android: name: Build Android steps: - name: Install dependencies run: yarn install
- name: Build Android run: | cd android ./gradlew assembleReleaseNote: Yarn lockfile (yarn.lock) is automatically cached by RunnerHub. If you use pnpm, add a setup step to install it first: npm install -g pnpm, then use pnpm install (pnpm lockfile pnpm-lock.yaml is also cached automatically).
If your React Native app is in a subdirectory using a monorepo structure:
name: React Native Monorepoplatform: react-native
environment: node: lts xcode: "16.4" android_sdk: 34
triggers: - push - pull_request
jobs: build-ios: name: Build iOS steps: - name: Install dependencies run: npm install working_directory: .
- name: Install pods run: pod install working_directory: apps/mobile/ios
- name: Build iOS run: xcodebuild -workspace App.xcworkspace -scheme App build working_directory: apps/mobile/ios
build-android: name: Build Android steps: - name: Install dependencies run: npm install working_directory: .
- name: Build Android run: ./gradlew bundleRelease working_directory: apps/mobile/androidThe working_directory field lets you run commands in subdirectories. This is useful for monorepos where the main package.json is at the root, but native build files are in subdirectories.
RunnerHub automatically caches all your dependencies:
package-lock.json, yarn.lock, or pnpm-lock.yamlPodfile.lock (iOS)node_modules/.cache/metro (React Native specific)After the first build, subsequent builds with the same dependencies are nearly instant. See the Caching guide for details.
For App Store, TestFlight, Google Play, or Firebase distribution:
name: React Native Releaseplatform: react-native
environment: node: lts xcode: "16.4" android_sdk: 34
triggers: - push branches: - main - release/*
jobs: build-ios-release: name: Build iOS for TestFlight steps: - name: Install dependencies run: npm install
- name: Install pods run: | cd ios pod install
- name: Build iOS Release run: | cd ios xcodebuild \ -workspace App.xcworkspace \ -scheme App \ -configuration Release \ -derivedDataPath build \ build
artifacts: - ios/build/**/*.app - ios/build/**/*.dSYM
build-android-release: name: Build Android for Google Play steps: - name: Install dependencies run: npm install
- name: Build Android Release Bundle run: | cd android ./gradlew bundleRelease
artifacts: - android/app/build/outputs/bundle/release/**/*.aabOnce signed via the RunnerHub Code Signing tab, you can attach Deploy configurations to automatically upload to TestFlight (iOS) or Google Play (Android).
Add secrets and configuration via the dashboard:
steps: - name: Build run: | cd ios xcodebuild -workspace App.xcworkspace -scheme App build env: API_URL: $API_URL API_KEY: $API_KEYFinding your scheme name:
xcodebuild -workspace ios/App.xcworkspace -listDebugging pod issues:
- name: Debug pods run: | cd ios pod repo update pod install --repo-updateBuilding for a specific simulator:
- name: Build for iPhone 15 run: | cd ios xcodebuild \ -workspace App.xcworkspace \ -scheme App \ -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 15' \ buildMetro bundler:
Metro bundler cache is automatically cached at node_modules/.cache/metro and restored between builds using the platform and branch as keys. This provides significant speedup for subsequent builds on the same branch.
If you need to pre-bundle or use explicit bundling steps:
- name: Bundle run: | npx react-native bundle \ --platform ios \ --dev false \ --entry-file index.js \ --bundle-output ios/main.jsbundle \ --assets-dest ios
- name: Build run: | cd ios xcodebuild -workspace App.xcworkspace -scheme App buildNode version: Your specified environment.node version is automatically activated before any steps run. No need to manually set up fnm, nvm, or nodenv — it’s pre-configured on the RunnerHub agents.
Code Signing
Ready to sign and distribute? Set up Apple + Android signing for your app.
Dual App Setup
On the dashboard, create a React Native app to enable dual signing (both Apple and Android) in one place.
Advanced Recipes
Learn multi-job strategies, matrix builds, and platform-specific patterns in the React Native cookbook.
Pipeline Reference
Explore all YAML options, including environment.node, environment.ruby, and multi-job DAGs in the pipeline reference.