Mobile CI/CD Pipelines
Build CI/CD pipelines that handle the unique challenges of mobile development: signing, provisioning, app store submission, beta distribution, and multi-platform builds. Covers Fastlane, Xcode Cloud, build optimization, and release management.
Mobile CI/CD is harder than server-side CI/CD. You cannot just build and deploy — you must sign, provision, build for multiple architectures, submit to review gatekeepers (Apple, Google), and manage a complex distribution matrix of beta testers, internal builds, and production releases.
Pipeline Architecture
PR Pipeline (every push):
lint → unit tests → build check → code coverage → report
Merge Pipeline (merge to main):
lint → unit tests → integration tests → build → upload to beta
Release Pipeline (tag):
lint → all tests → build release → sign → submit to app stores
Fastlane Configuration
iOS
# Fastfile
platform :ios do
desc "Run unit tests"
lane :test do
scan(
scheme: "GarnetApp",
devices: ["iPhone 15 Pro"],
code_coverage: true,
output_types: "html,junit"
)
end
desc "Build and distribute to TestFlight"
lane :beta do
increment_build_number
match(type: "appstore") # Automatic code signing
gym(
scheme: "GarnetApp",
configuration: "Release",
export_options: {
method: "app-store",
provisioningProfiles: {
"com.garnet.app" => "match AppStore com.garnet.app"
}
}
)
pilot(
skip_waiting_for_build_processing: true,
changelog: changelog_from_git_commits
)
slack(message: "New beta build uploaded to TestFlight!")
end
desc "Submit to App Store"
lane :release do
build_number = increment_build_number
match(type: "appstore")
gym(scheme: "GarnetApp", configuration: "Release")
deliver(
submit_for_review: true,
automatic_release: false,
force: true,
metadata_path: "./fastlane/metadata"
)
end
end
Android
platform :android do
desc "Run unit tests"
lane :test do
gradle(task: "testDebugUnitTest")
end
desc "Build and distribute to Play Store Internal"
lane :beta do
gradle(
task: "bundleRelease",
properties: {
"android.injected.signing.store.file" => ENV["KEYSTORE_PATH"],
"android.injected.signing.store.password" => ENV["STORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
"android.injected.signing.key.password" => ENV["KEY_PASSWORD"]
}
)
upload_to_play_store(
track: "internal",
aab: "app/build/outputs/bundle/release/app-release.aab"
)
end
desc "Promote internal to production"
lane :release do
upload_to_play_store(
track: "internal",
track_promote_to: "production",
rollout: "0.1" # 10% rollout
)
end
end
Code Signing
iOS Code Signing (match)
# Matchfile
storage_mode("git")
git_url("https://github.com/org/certificates")
type("appstore")
app_identifier("com.garnet.app")
team_id("TEAM123")
Match workflow:
1. Stores certificates and profiles in encrypted Git repo
2. Every developer and CI machine pulls the same profiles
3. No more "Signing for X requires a development team" errors
4. One source of truth for all signing identities
Android Signing
# GitHub Actions: Decode and use keystore
- name: Decode keystore
run: echo ${{ secrets.KEYSTORE_BASE64 }} | base64 -d > keystore.jks
- name: Build release
run: ./gradlew bundleRelease
env:
KEYSTORE_PATH: keystore.jks
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
Build Optimization
| Technique | iOS Impact | Android Impact |
|---|---|---|
| Incremental builds | 40-60% faster | 30-50% faster |
| Module caching | 50%+ faster (SPM) | 40%+ faster (Gradle) |
| Parallel testing | 3-4x faster (multiple sims) | 2-3x faster (sharding) |
| Build cache (CI) | 30-50% faster | 40-60% faster |
| ccache/sccache | 50-70% faster (C/C++ deps) | N/A |
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| Manual code signing | ”Works on my machine” | Fastlane match (iOS), CI keystore (Android) |
| No beta distribution | Only test on dev devices | TestFlight + Play Store Internal Track |
| Build everything on every PR | 30+ min CI | Build check only, full build on merge |
| No build caching | Slow CI, wasted compute | Cache derived data (iOS), Gradle cache (Android) |
| Manual App Store submission | Error-prone, slow | Fastlane deliver / supply |
Mobile CI/CD pipelines are more complex than server-side. Invest in automation early — the time savings compound with every release.