React Native Advanced: CI/CD Pipeline from Zero to Production
Flow-wise guide covering market analysis, org-level CI/CD, GitHub workflows, SEMVER, Jest & Detox, Husky, GCP Play Console, Fastlane, GitHub Actions, Genymotion SaaS, and live pipelines.
From tutorials to production pipelines
Beginner React Native tutorials teach components, navigation, and state. Production mobile teams need more: automated testing, signed builds, store releases, crash monitoring, and pipelines that ship without manual Xcode or Android Studio clicks.
A solo developer can manually archive an IPA or generate an APK. A team of five cannot — merge conflicts, inconsistent signing, and untested native changes create release risk. CI/CD turns shipping from a heroic Friday-night event into a repeatable, auditable process.
This course walks through the full org-level CI/CD flow used by professional React Native teams — from market context to a live pipeline running on every pull request.
The gap between 'it works locally' and production
- Local builds use debug keystores; production needs upload keys, provisioning profiles, and Play App Signing.
- Metro bundler hides issues that surface only in release builds (ProGuard, Hermes, minification).
- Store review rejects duplicate build numbers, missing privacy manifests, and unsigned entitlements.
- Without CI, nobody knows if main is buildable until someone tries to release.
What you will build in this course
- Understand the RN job market and skills employers expect at each level.
- Design org-level CI/CD with branching, SEMVER, and release trains.
- Configure Jest, Detox, Husky, Fastlane, GitHub Actions, and Genymotion SaaS.
- Ship your first signed AAB to Play Console and TestFlight via automation.
- Run a live pipeline with Discord notifications and nightly E2E tests.
React Native in the job market
React Native remains one of the highest-demand mobile skills because one codebase targets iOS and Android. Companies from startups to enterprises use it for customer apps, internal tools, and MVPs that need fast iteration.
Job postings increasingly list CI/CD, Detox/Appium, Fastlane, and store release experience alongside React hooks and native modules. 'Can you ship to the store?' is as common as 'Can you build a screen?'
India and remote-first companies hire heavily for RN + TypeScript stacks. US and EU roles often expect pipeline ownership at senior levels.
Role levels and salary bands (2025–2026)
- Junior RN (0–2 yrs): UI screens, API integration, basic Jest — India ₹4–8 LPA, US $70–95k.
- Mid-level (2–5 yrs): navigation architecture, performance, offline sync, CI familiarity — ₹10–18 LPA / $100–130k.
- Senior / Lead (5+ yrs): release trains, monorepo tooling, native bridging, pipeline ownership — ₹20–35+ LPA / $140–180k+.
- Contract / freelance: $50–120/hr depending on CI/CD and native module depth.
Skills that differentiate candidates
- Automated store uploads via Fastlane or EAS — not just 'I built the APK manually'.
- Semantic versioning discipline and changelog hygiene for multi-team apps.
- E2E tests running on CI (Detox, Maestro, or Appium) with flaky-test management.
- Incident response for bad releases — staged rollouts, hotfix branches, rollback runbooks.
- Familiarity with both platforms: Gradle/Xcode configs, permissions, deep links.
What to learn first for job readiness
Priority order: solid RN fundamentals → Jest unit tests → git workflow → one complete staging deploy (Play Internal or TestFlight) → then full CI/CD automation. Employers value a working pipeline demo in your portfolio over theoretical DevOps knowledge.
CI/CD is a team contract
At org scale, CI/CD is not 'run npm test on GitHub.' It is a contract between engineering, QA, product, and operations. Everyone agrees on: who merges to main, who approves releases, how hotfixes branch, where artifacts live, and what 'done' means for a release.
Without this contract, you get shadow releases (someone builds locally), untested main branches, and blame games when production crashes.
Org-level principles
- Trunk-based development with short-lived feature branches — standard for mobile teams shipping weekly.
- Long-lived release branches only when maintaining multiple app versions live in stores simultaneously.
- Every merge to main must produce a testable build on Internal / TestFlight within 30 minutes.
- Separate pipelines by speed: PR validation (<15 min) vs nightly (full E2E) vs release (signed + upload).
- Secrets in vaults — GitHub Encrypted Secrets, GCP Secret Manager, 1Password — never in git history.
- Artifact retention policy: keep last N release APKs/IPAs for rollback comparison.
Roles and responsibilities
- Developers: write tests, keep PRs green, document native changes.
- QA: define smoke vs regression suites, sign off staging before production promotion.
- Release manager / tech lead: cut release branches, approve SEMVER bumps, coordinate store metadata.
- DevOps / platform: maintain runners, secrets rotation, pipeline cost optimization.
End-to-end pipeline flow
The best RN CI/CD flow balances speed on pull requests with thoroughness before store release. Fast feedback keeps developers unblocked; staged gates prevent bad binaries reaching users.
Below is the complete flow from local commit to production — study this diagram before configuring any tool.
Stage-by-stage breakdown
- Local: Husky runs lint + unit tests on every commit — catches 60% of issues before CI.
- PR: GitHub Actions runs typecheck, Jest, Android release build (cached deps). Optional iOS sim build on macOS runner.
- Develop merge: Fastlane uploads to Play Internal + TestFlight; Firebase App Distribution for QA outside store.
- Main merge: SEMVER bump, git tag, signed release build, changelog generated from commits.
- Notification: Discord/Slack webhook with version, branch, commit SHA, and artifact links.
- Production: manual promotion in Play Console (staged rollout 5% → 20% → 100%) and App Store Connect.
Pipeline timing targets
- PR pipeline: under 15 minutes (lint + test + Android build).
- Staging deploy after develop merge: under 45 minutes (includes signing).
- Full release pipeline: under 90 minutes (both platforms + store processing wait).
- Nightly E2E: 30–60 minutes depending on test count and Genymotion instance size.
Branch strategy for mobile teams
Mobile apps need predictable branches because store binaries map to specific commits. GitFlow-lite (main + develop + feature + release + hotfix) works well for teams with scheduled releases.
Branch definitions
- main — always deployable; protected; requires PR + all status checks + 1 approval.
- develop — integration branch; auto-deploys to staging on every merge.
- feature/JIRA-123-login — one ticket per branch; max 3–5 days lifespan; squash merge.
- release/2.4.0 — cut from develop when feature freeze; only bugfixes allowed.
- hotfix/crash-on-launch — branch from main tag; PATCH version bump; merge to main AND develop.
git checkout develop && git pull
git checkout -b release/2.4.0
# bump version in app.json, update CHANGELOG.md
git commit -am "chore: prepare release 2.4.0"
git push -u origin release/2.4.0
# after QA approval:
git checkout main && git merge release/2.4.0
git tag v2.4.0 && git push origin main --tags
git checkout develop && git merge release/2.4.0Hotfix workflow (urgent production fix)
- Branch hotfix/2.4.1 from main at the current production tag.
- Fix, test, bump PATCH version, merge to main, tag v2.4.1, trigger release pipeline.
- Cherry-pick or merge hotfix back to develop so the fix is not lost in next release.
- Document in post-mortem: root cause, test added, rollout percentage when issue detected.
Repository standards
Mobile repos have unique risk: native folders, signing configs, and large binary assets. GitHub settings and templates enforce consistency across contributors.
Required repository configuration
- Branch protection on main: require PR, dismiss stale reviews, require status checks (lint, test, android-build).
- CODEOWNERS: @mobile-leads for ios/, android/, fastlane/, .github/workflows/.
- PR template: screenshots/video, test plan, version impact, devices tested, migration notes.
- Issue templates: bug (steps, OS, build version), feature, release checklist.
- Dependabot: enable for npm and bundler (Fastlane); review native dependency bumps in android/build.gradle.
- Never commit: prod google-services.json, .jks keystores, .p12 certificates, API keys.
PR review checklist for reviewers
- Does this change need a native rebuild? (new package with native code = yes)
- Are testID props added for new interactive UI (Detox)?
- Does Android need ProGuard keep rules? Does iOS need Info.plist entries?
- Will this change require a MAJOR/MINOR/PATCH version bump?
- Are screenshots attached for UI changes?
Tie git tags to store builds
Mobile apps ship binaries, not just source. Every production build must trace back to an exact git commit and tag. When a crash report says 'version 2.4.0 build 20400', your team must checkout that tag and reproduce.
Maintain one source of truth for version strings — typically app.json, package.json, or a dedicated version.json updated by Fastlane.
Version fields explained
- versionName / CFBundleShortVersionString — user-visible '2.4.0' on store listing.
- versionCode / CFBundleVersion — integer build number; must always increase per upload.
- Git tag v2.4.0 — immutable pointer to the release commit.
- CI build number — optional fourth identifier for internal tracking (e.g. CI run ID).
{
"expo": {
"version": "2.4.0",
"ios": { "buildNumber": "20400" },
"android": { "versionCode": 20400 }
}
}increment_version_number(bump_type: "patch")
increment_build_number(xcodeproj: "ios/MyApp.xcodeproj")
# Android: update versionCode in build.gradle via gradle helperChangelog discipline
- Keep CHANGELOG.md with sections: Added, Changed, Fixed, Removed per release.
- Auto-generate from conventional commits using standard-version or release-please GitHub Action.
- Paste changelog into Play Console 'Release notes' and App Store 'What's New' fields.
MAJOR.MINOR.PATCH explained
SEMVER (semver.org) uses MAJOR.MINOR.PATCH (e.g. 2.4.1). Users and support teams rely on version numbers to understand change risk. Mobile adds a separate monotonic build number required by stores.
Pre-release versions: 2.5.0-beta.1, 2.5.0-rc.2 — useful for TestFlight and Play Open Testing tracks.
When to bump each segment
- MAJOR (3.0.0): breaking API changes, removed features, minimum OS version bump, major redesign.
- MINOR (2.5.0): new screens, features, non-breaking native module updates, new permissions.
- PATCH (2.4.1): crash fixes, copy changes, hotfixes, performance tweaks without new features.
- Build number: increment on EVERY store upload, even if marketing version unchanged (iOS resubmit).
feat: add biometric login → MINOR (2.4.0 → 2.5.0)
fix: crash on logout → PATCH (2.4.0 → 2.4.1)
feat!: remove legacy auth API → MAJOR (2.4.0 → 3.0.0)
docs: update README → no version bump
chore: bump eslint → no version bump (usually)DORA metrics for mobile
Engineering managers use DORA (DevOps Research and Assessment) metrics to measure delivery health. Mobile teams face unique constraints — store review times, signing complexity, and device fragmentation — but the metrics still apply.
A mature RN CI/CD pipeline reduces 'it works on my machine' releases and makes rollback predictable.
What managers should track
- Deployment frequency: how often you ship to production (weekly is healthy for mobile).
- Lead time for changes: commit to production — target under 1 week for non-hotfix.
- Change failure rate: % of releases causing incidents — target under 15%.
- Mean time to recovery: hours from incident to hotfix in stores — target under 4 hours.
- CI cost per month: Mac runner minutes, Genymotion SaaS, artifact storage.
Investment decisions
- Staging environment ROI: one prevented bad release pays for months of CI infrastructure.
- Release cadence: weekly trains reduce batch size and make debugging easier.
- QA gates: automate regression; reserve human QA for UX, accessibility, and store listing.
- Runbooks: document hotfix procedure, on-call rotation, and store rollback steps.
Choosing your toolchain
No single tool does everything. CI platform runs workflows; Fastlane handles mobile-specific release steps; Detox runs E2E; Genymotion provides cloud devices. Choose based on team size, budget, and existing infrastructure.
CI platform comparison
- GitHub Actions — best if code is on GitHub; free tier generous; macOS runners $0.08/min; huge action marketplace.
- Bitrise — mobile-first UI; stacks pre-configured for RN; $36+/mo; less YAML, more clicking.
- Codemagic — RN/Flutter focused; M1 Mac runners; simple yaml; good for small teams without DevOps.
- CircleCI / GitLab CI — enterprise; self-hosted runners for air-gapped or compliance requirements.
Supporting tools
- Fastlane — lanes for build, sign, upload, screenshots; works inside any CI.
- EAS Build (Expo) — managed cloud builds + submit; less config, less control.
- Detox — gray-box E2E; syncs with React Native bridge.
- Maestro — YAML-based E2E alternative; easier setup, growing adoption.
- Firebase App Distribution — distribute APK/IPA to testers without store tracks.
- Sentry / Crashlytics — crash monitoring post-release; integrate in CI for source map upload.
Testing pyramid for React Native
Unit tests (Jest) are fast and run on every PR. Integration tests cover navigation and API layers. E2E tests (Detox) validate critical user journeys on real simulators — run nightly or pre-release due to cost.
Aim for 70%+ unit coverage on business logic; 5–10 Detox flows covering login, checkout, and core navigation.
Jest unit test setup
npm install --save-dev jest @testing-library/react-native @types/jest
# Detox (E2E)
npm install --save-dev detox detox-cli
# iOS simulator utils
brew tap wix/brew && brew install applesimutilsmodule.exports = {
preset: "react-native",
setupFilesAfterSetup: ["@testing-library/jest-native/extend-expect"],
transformIgnorePatterns: [
"node_modules/(?!(react-native|@react-native|@react-navigation)/)"
],
collectCoverageFrom: ["src/**/*.{ts,tsx}", "!src/**/*.d.ts"],
};import { render, screen } from "@testing-library/react-native";
import { LoginButton } from "./LoginButton";
it("renders login label", () => {
render(<LoginButton onPress={() => {}} />);
expect(screen.getByText("Sign In")).toBeTruthy();
});Detox E2E setup
{
"scripts": {
"test": "jest",
"detox:build:android": "detox build -c android.emu.debug",
"detox:test:android": "detox test -c android.emu.debug",
"detox:build:ios": "detox build -c ios.sim.debug",
"detox:test:ios": "detox test -c ios.sim.debug"
}
}module.exports = {
apps: {
"android.debug": {
type: "android.apk",
binaryPath: "android/app/build/outputs/apk/debug/app-debug.apk",
build: "cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug"
},
"ios.debug": {
type: "ios.app",
binaryPath: "ios/build/Build/Products/Debug-iphonesimulator/MyApp.app",
build: "xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build"
}
},
devices: {
emulator: { type: "android.emulator", device: { avdName: "Pixel_6_API_34" } },
simulator: { type: "ios.simulator", device: { type: "iPhone 15" } }
},
configurations: {
"android.emu.debug": { device: "emulator", app: "android.debug" },
"ios.sim.debug": { device: "simulator", app: "ios.debug" }
}
};// App: <TextInput testID="email-input" />
describe("Login flow", () => {
beforeAll(async () => { await device.launchApp(); });
it("shows home after valid login", async () => {
await element(by.id("email-input")).typeText("user@test.com");
await element(by.id("password-input")).typeText("secret");
await element(by.id("login-button")).tap();
await expect(element(by.id("home-screen"))).toBeVisible();
});
});Catch issues before CI
CI minutes cost money and slow feedback loops. Husky runs git hooks locally so broken lint and failing unit tests never get pushed. Pair with lint-staged to only scan staged files — typically completes in under 10 seconds.
Installation and configuration
npm install --save-dev husky lint-staged
npx husky init
echo "npx lint-staged" > .husky/pre-commit
# optional: run tests on push
echo "npm test -- --passWithNoTests" > .husky/pre-push{
"lint-staged": {
"*.{ts,tsx,js,jsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"],
"android/**/*.gradle": ["echo 'Review native changes manually'"]
}
}Best practices
- Keep pre-commit fast — lint and format only; run full test suite on pre-push or CI.
- Document how to skip hooks in emergencies: git commit --no-verify (use sparingly).
- Ensure CI runs the same checks as hooks — no drift between local and remote.
- Add commit-msg hook for conventional commit format if using automated versioning.
Android release prerequisites
Every Android release flows through Google Play Console. Automated uploads require a GCP service account linked to your Play Developer account. Manual first upload is recommended to understand the console before automating.
Step-by-step setup
- Create Google Play Developer account at play.google.com/console ($25 one-time fee).
- Create GCP project (or use existing) at console.cloud.google.com.
- Play Console → Setup → API access → Link the GCP project.
- GCP → IAM → Create service account (e.g. play-deploy-bot).
- Play Console → Users and permissions → Invite service account email.
- Grant: Release to testing tracks, View app information (not financial data).
- GCP → Service account → Keys → Add key → JSON → download once securely.
- Store JSON in GitHub Secret; in CI write to temp file with chmod 600.
- name: Decode Play Store key
run: |
echo "${{ secrets.PLAY_STORE_JSON_KEY }}" > play-key.json
chmod 600 play-key.json
env:
PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }}Before first upload checklist
- App listing: title, description, screenshots (phone + tablet), feature graphic.
- Content rating questionnaire completed.
- Data safety form filled (data collection, sharing, encryption).
- Privacy policy URL hosted and linked.
- Target API level meets Play requirements (check current Android target SDK policy).
- Play App Signing enabled (recommended for all new apps).
Why AAB, not APK
Google Play requires Android App Bundle (AAB) for new apps since 2021. Google generates optimized APKs per device configuration (screen density, ABI, language). You upload one AAB; Play serves the right APK to each user.
Release builds must be signed with your upload keystore. Play App Signing re-signs with Google's key for distribution.
Signing and build configuration
keytool -genkeypair -v -storetype PKCS12 \
-keystore android/app/upload-keystore.jks \
-alias upload -keyalg RSA -keysize 2048 -validity 10000signingConfigs {
release {
storeFile file(MYAPP_UPLOAD_STORE_FILE)
storePassword MYAPP_UPLOAD_STORE_PASSWORD
keyAlias MYAPP_UPLOAD_KEY_ALIAS
keyPassword MYAPP_UPLOAD_KEY_PASSWORD
}
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled true
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}cd android && ./gradlew bundleRelease
# Output: android/app/build/outputs/bundle/release/app-release.aab
# Verify: bundletool validate --bundle=app-release.aabManual first upload steps
- Play Console → Your app → Testing → Internal testing → Create new release.
- Upload app-release.aab; add release notes.
- Create email list of testers; share opt-in link.
- Review and roll out to internal track.
- Install on physical device via opt-in link; verify before automating.
Fastlane — the mobile release automation layer
Fastlane is a Ruby-based tool with 'lanes' — scripts that chain build, sign, test, and upload steps. It runs locally and in CI identically. One Fastfile per platform (android/, ios/) keeps concerns separated.
Project setup
# macOS (recommended for iOS lanes)
brew install fastlane
# Project-local Gemfile approach (CI-friendly)
cd android && fastlane init
cd ios && fastlane init
# Commit Gemfile.lock for reproducible CI buildsdefault_platform(:android)
platform :android do
desc "Deploy to Play Internal testing"
lane :internal do
increment_version_code(app_project_dir: "app")
gradle(task: "bundle", build_type: "Release")
upload_to_play_store(
track: "internal",
aab: "app/build/outputs/bundle/release/app-release.aab",
json_key: ENV["PLAY_STORE_JSON_KEY_PATH"],
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
enddefault_platform(:ios)
platform :ios do
desc "Push to TestFlight"
lane :beta do
setup_ci if ENV["CI"]
match(type: "appstore", readonly: true) # sync certs from git or cloud
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(workspace: "MyApp.xcworkspace", scheme: "MyApp", export_method: "app-store")
upload_to_testflight(skip_waiting_for_build_processing: true)
end
endCommon Fastlane actions
- match — sync code signing certs and profiles across team via encrypted git repo.
- gradle / build_app — compile release binaries.
- upload_to_play_store / upload_to_testflight — store uploads.
- snapshot — automated App Store screenshots across device sizes.
- slack / discord — post lane success/failure from Fastfile directly.
Real-time team visibility
Mobile releases involve QA, product, and support — not just developers. Discord webhooks push build results to a channel instantly. Include version, branch, commit, author, and link to CI run for one-click debugging.
Setup Discord webhook
- Discord server → Channel settings → Integrations → Webhooks → New Webhook.
- Copy webhook URL; add as DISCORD_WEBHOOK in GitHub Secrets.
- Create #mobile-releases channel for staging; #mobile-alerts for failures.
- Never expose webhook URL in public repos — rotate if leaked.
- name: Notify Discord
if: always()
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
STATUS: ${{ job.status }}
run: |
COLOR=3066993
[ "$STATUS" = "failure" ] && COLOR=15158332
curl -H "Content-Type: application/json" -X POST "$DISCORD_WEBHOOK" -d "{
\"embeds\": [{
\"title\": \"📱 RN Build: $STATUS\",
\"description\": \"**Branch:** ${{ github.ref_name }}\n**Commit:** ${{ github.sha }}\n**Author:** ${{ github.actor }}\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\",
\"color\": $COLOR,
\"footer\": { \"text\": \"Rishtaara CI\" }
}]
}"Production-style workflow architecture
Split jobs by concern: validate (fast, blocks merge), platform builds (parallel), release (main only). Use needs: for dependencies, actions/cache for npm and Gradle, and matrix strategy only when testing multiple Node/Java versions.
Complete workflow example
name: Mobile CI
on:
pull_request:
branches: [main, develop]
push:
branches: [main, develop]
concurrency:
group: mobile-${{ github.ref }}
cancel-in-progress: true
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint
- run: npm test -- --ci --coverage
android-build:
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- uses: actions/setup-java@v4
with: { distribution: temurin, java-version: 17, cache: gradle }
- run: npm ci
- run: cd android && ./gradlew assembleRelease --no-daemon
- uses: actions/upload-artifact@v4
with:
name: android-release-apk
path: android/app/build/outputs/apk/release/
retention-days: 14
ios-build:
needs: validate
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: cd ios && pod install
- run: |
xcodebuild -workspace MyApp.xcworkspace -scheme MyApp \
-sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15' \
-configuration Release buildOptimization tips
- Use concurrency to cancel outdated PR runs when new commits push.
- Cache Gradle (~/.gradle) and CocoaPods (Pods/) directories.
- Run iOS builds only when ios/ or package.json changes (paths filter).
- Store signing secrets as base64 in GitHub Secrets; decode in workflow.
- Separate release workflow triggered only on tags matching v*.*.*.
E2E testing in the cloud
GitHub's ubuntu-latest runners do not include Android emulators with KVM by default. Genymotion SaaS provides cloud Android devices accessible via adb — ideal for Detox E2E in CI without maintaining your own emulator farm.
Cost model: pay per minute of device usage. Run full suite nightly (~30 min); smoke tests only on release branches.
Setup steps
- Create account at cloud.geny.io; generate API token.
- Install gmsaas CLI locally to test: brew install genymotion/genymotion/gmsaas.
- Add GENYMOTION_API_TOKEN to GitHub Secrets.
- In workflow: auth → start instance → adb connect → detox build → detox test → stop instance.
- Pin device profile (e.g. Samsung Galaxy S24, Android 14) for consistent results.
e2e-nightly:
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- name: Start Genymotion cloud device
env:
GENYMOTION_API_TOKEN: ${{ secrets.GENYMOTION_API_TOKEN }}
run: |
gmsaas auth login "$GENYMOTION_API_TOKEN"
INSTANCE=$(gmsaas instances start "Samsung Galaxy S24" "android-14")
gmsaas instances adbconnect "$INSTANCE"
- run: npm run detox:build:android
- run: npm run detox:test:android
- name: Stop instance
if: always()
run: gmsaas instances stop "$INSTANCE"Flaky test management
- Retry failed Detox tests once in CI before marking job failed.
- Record videos/screenshots on failure for debugging.
- Quarantine flaky tests with testNamePattern skip until fixed.
- Track flake rate; aim for under 2% retry rate on nightly runs.
A day in a live pipeline
When all pieces are connected, shipping becomes routine. Below is a realistic timeline for a team using GitHub Actions, Fastlane, Discord, and nightly Detox on Genymotion.
Hour-by-hour walkthrough
- 09:00 — Developer opens PR for feature/login-v2. Husky runs ESLint + Prettier on staged files.
- 09:05 — GitHub Actions validate job passes (lint, Jest, 82% coverage). android-build uploads artifact.
- 10:30 — Code review approved; squash merge to develop.
- 11:00 — Develop pipeline triggers Fastlane internal lane. AAB → Play Internal. IPA → TestFlight.
- 11:01 — Discord #mobile-releases: 'Staging 2.5.0 (20500) ready — login v2 + biometric'.
- 14:00 — QA completes smoke checklist on Pixel + iPhone. Signs off in release ticket.
- 14:30 — Release manager cuts release/2.5.0, bumps SEMVER, updates CHANGELOG.
- 16:00 — Merge to main. Tag v2.5.0. Release workflow uploads signed builds.
- 16:30 — Product promotes Play staged rollout 5%. App Store submission for review.
- 02:00 — Nightly cron runs full Detox suite on Genymotion. 47 tests pass in 38 minutes.
Incremental adoption roadmap
- Week 1: Husky + Jest + lint on PR.
- Week 2: Android assembleRelease on PR; artifact upload.
- Week 3: Fastlane internal lane on develop merge.
- Week 4: iOS sim build on macOS runner.
- Week 5: Discord notifications + Play/TestFlight automation.
- Week 6: Nightly Detox on Genymotion; release workflow on tags.
Practice and certify your knowledge
- MCQ Quiz: /mcq/react-native-ci-cd-mcq — 15 questions on pipeline concepts.
- Interview prep: /interview/react-native-ci-cd-interview — 20 Q&A for senior roles.
- Full reference: /knowledge/react-native-advanced-ci-cd — combined guide with all sections.
Key Takeaways
- Org-level CI/CD defines branching, secrets, release cadence, and store promotion gates.
- SEMVER + monotonic build numbers prevent store rejections and enable traceable hotfixes.
- Husky + Jest on every PR; Detox + Genymotion on nightly or pre-release saves CI cost.
- Fastlane is the standard bridge from RN builds to Play Console and TestFlight.
- GitHub Actions + Discord webhooks give team visibility; managers track DORA metrics.
- Adopt pipelines incrementally — lint first, then builds, then store automation, then E2E.
Frequently Asked Questions
- Do I need a Mac for Android-only CI?
- No. Android builds run on Linux GitHub runners. iOS requires macOS runners or a cloud service like Codemagic/EAS.
- Expo vs bare React Native for CI/CD?
- Expo EAS simplifies cloud builds with managed credentials. Bare RN + Fastlane offers maximum control for custom native code and enterprise DevOps standards.
- How often should Detox E2E run?
- Smoke E2E on release branches; full suite nightly. Running on every PR is ideal but expensive on cloud emulators.
- Where should keystore and API keys live?
- GitHub Encrypted Secrets for CI, plus offline backup of upload keystore in a team vault. Never commit keys to git history.
- What is the minimum viable RN CI pipeline?
- npm ci → lint → Jest → Android assembleRelease on every PR. Add iOS, Fastlane, and E2E in subsequent sprints.