Web teams have had CI/CD figured out for years. Push code, run tests, deploy. Mobile is different. You are dealing with code signing certificates, provisioning profiles, platform-specific build tools, app store review processes, and device fragmentation. A pipeline that works for a React app will not survive contact with Xcode.
At DEVSFLOW, every client project ships through an automated pipeline from day one. Here is the architecture we use and why each piece matters.
The Pipeline Overview
Our standard mobile CI/CD pipeline has five stages:
- 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
Every stage is automated except the final submission. We keep that manual because App Store review is not instant and timing matters for coordinated launches.
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
We run most pipelines on GitHub Actions with macOS runners for iOS and Linux runners for Android. The split matters: macOS runners cost 10x more per minute than Linux. Running Android builds on macOS is burning money for no reason.
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 the App Store. We integrate Firebase Crashlytics into every project and set up Slack alerts for crash spikes. If the crash-free rate drops below 99.5% after a release, the team is notified within minutes.
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 mobile builds are still manual or your pipeline is held together with workarounds, let us set it up properly. We can have a production-grade pipeline running in your repo within a week.