A mobile pipeline has two jobs: tell a developer quickly whether a change is safe to merge, and produce a release artifact that can be traced back to reviewed source. Signing, platform toolchains, store submission, and physical-device behavior make that harder than copying a web deployment workflow.
The pipeline below is a starting architecture. The exact gates should follow the product's risks, not a universal checklist.
The pipeline overview
A useful mobile pipeline usually separates five concerns:
- Lint and static analysis on every pull request
- Unit tests run automatically before merge
- Build for both debug and release configurations
- Distribution to internal testers via TestFlight and Firebase App Distribution
- App Store submission triggered manually after QA sign-off
Whether store submission is automatic is a release-policy choice. Some teams require a named release owner to approve production submission. Others submit automatically after a signed tag and keep only the phased rollout manual. Make that boundary explicit.
Code signing: the hardest part
iOS code signing is the single biggest source of CI/CD failures in mobile. Certificates expire. Provisioning profiles get revoked. A developer's local keychain works but the CI machine's does not.
We solve this with Fastlane Match. All certificates and provisioning profiles live in an encrypted private Git repository. The CI machine pulls them fresh on every build. When a certificate expires, we regenerate it once and every pipeline picks it up automatically.
If your iOS builds work on one developer's machine but not another's, your code signing is not automated. Fix this before anything else.
For Android, we store the keystore file and signing credentials as encrypted CI secrets. Gradle reads them from environment variables at build time. The keystore never lives in the source repository.
Fastlane: the backbone
Fastlane orchestrates everything between "code compiled" and "build distributed." Our standard Fastfile includes lanes for:
- test: Runs unit tests with coverage reporting
- beta: Builds, signs, and uploads to TestFlight or Firebase App Distribution
- release: Bumps version, builds release, uploads to App Store Connect or Google Play Console
- screenshots: Generates localized App Store screenshots automatically
We version-lock Fastlane with Bundler and pin plugin versions. An uncontrolled Fastlane update has broken more CI pipelines than actual code bugs.
GitHub Actions: our CI platform
Use macOS runners where Xcode is required and Linux runners for Android-only work. Hosted-runner prices and multipliers change, so calculate the current cost from your CI provider instead of carrying an old ratio into the architecture.
Key optimizations we apply:
- Dependency caching: CocoaPods, Gradle dependencies, and Fastlane gems are cached between runs. A cache hit saves 3-5 minutes per build.
- Parallel test execution: Android unit tests run on Linux while iOS tests run on macOS simultaneously.
- Selective builds: If only Android files changed, the iOS pipeline does not run. We use path filters in the workflow triggers.
- Build artifact reuse: The debug build artifact from the test stage is passed to the distribution stage. We do not rebuild.
Testing in CI: what actually works
Unit tests in CI are straightforward. UI tests are not. Emulator-based tests on CI are slow and flaky. Simulator tests on macOS are better but still add 10-15 minutes to a pipeline.
Our approach: run unit tests on every PR. Run UI tests nightly against the main branch. This catches regressions without slowing down the development feedback loop. For critical flows (login, payment, onboarding), we run a small UI test suite on every merge to main.
We use XCTest for iOS and JUnit with Espresso for Android. For React Native projects, Jest handles the JavaScript layer and Detox runs end-to-end tests on simulators.
Distribution: getting builds to testers
iOS: TestFlight is the standard. Every merge to the develop branch triggers a beta upload. Testers get a push notification. External testers require a brief Beta App Review, so we keep a standing external test group to avoid delays.
Android: Firebase App Distribution for internal builds, Google Play internal testing track for pre-release. Firebase is faster (no review), Play Console is better for staged rollouts.
Both platforms get build notes auto-generated from the Git commit log since the last release. Testers always know what changed.
Version management
We automate version bumping with a simple convention: the CI reads the version from a single source of truth (a version file or the build config), increments the build number on every CI run, and tags the commit.
Marketing version (1.2.0) is bumped manually when a release is planned. Build number (142, 143, 144) increments automatically. This means every TestFlight and Firebase build has a unique identifier with zero manual work.
Monitoring after deploy
The pipeline does not end at store upload. Connect each release to crash reporting, API health, and rollout status. Alert thresholds should be based on the product's baseline and traffic volume. A fixed crash-free percentage without a minimum sample size can create noise for a small release and hide a serious regression in a critical user journey.
For enterprise clients, we also monitor app size trends, startup time, and API error rates per app version. Regressions are caught before users report them.
If your release depends on one developer's laptop, contact DEVSFLOW. We can inspect the signing and release path, identify the first reproducible slice, and document what can be automated safely.
Want CI/CD, crash monitoring, and ongoing performance optimization handled for you? Our maintenance plans keep your mobile app healthy month after month. See our maintenance plans