ADR Reference: ADR-MOB-003
Mobile CI/CD presents unique engineering challenges absent from backend pipelines: iOS builds require macOS infrastructure, code signing involves certificates and provisioning profiles that expire and must be shared across a team, both app stores have multi-day review processes, and staged rollouts require coordination between the deployment pipeline and store dashboards. This ADR eliminates the class of failures that arise from ad-hoc, undocumented mobile release processes.
| Field |
Value |
| ADR Reference |
ADR-MOB-003 |
| Version |
1.0 |
| Date Raised |
May 2025 |
| Review Date |
November 2025 |
| Author |
Solutions Architecture Practice — Ascendion |
| Status |
ACCEPTED |
| Domain |
Mobile DevOps / Platform Engineering |
| ARB Approval |
Required |
| Stakeholders |
Mobile DevOps Engineers · Release Managers · Platform Engineers · Security Architects · QA Lead |
Executive Summary
Decision: Fastlane combined with GitHub Actions is adopted as the standard mobile CI/CD pipeline for all Ascendion mobile projects. Fastlane manages code signing via fastlane Match, build automation, test execution, and store submission. GitHub Actions orchestrates the pipeline using matrix strategy to run Android and iOS builds in parallel. Xcode Cloud is adopted as a supplementary option for iOS-only projects. The pipeline enforces test coverage gates from ADR-MOB-001: Use Cases above 90%, ViewModels above 80%. All production releases require QA sign-off as a mandatory pipeline gate.
Decision Drivers
| Priority |
Quality Attribute |
Weight |
Rationale |
| 1 |
Code Signing Reliability |
25% |
Signing failures are the most common iOS release delay cause |
| 2 |
Quality Gate Enforcement |
22% |
Test coverage and snapshot gates must be automated |
| 3 |
Pipeline Speed |
18% |
Target under 30 minutes total. Slow pipelines are bypassed |
| 4 |
Automation Coverage |
15% |
Build, test, sign, distribute, submit — zero manual steps |
| 5 |
Cost |
12% |
macOS runner cost is 10× Linux. Minimise macOS minutes |
| 6 |
Maintainability |
8% |
Pipeline as code, versioned in Git, operable by any engineer |
Considered Options
Option A — Fastlane + GitHub Actions (ADOPTED)
Weighted score: 4.81 / 5.0. fastlane Match solves code signing at the root — all certificates stored encrypted in a dedicated Git repo, every CI machine and developer clones fresh. GitHub Actions matrix runs Android (Linux) and iOS (macOS) in parallel, halving total pipeline time.
Option B — Bitrise
Weighted score: 3.46 / 5.0 — DISMISSED. Cost at scale ($450–900/month) exceeds GitHub Actions with self-hosted macOS runner. Vendor lock-in on pipeline configuration prevents migration.
Option C — Xcode Cloud (iOS SUPPLEMENT)
Apple-native CI. 25 free compute hours/month. Direct TestFlight integration. iOS/macOS only. Adopted as supplementary for iOS-only projects — cannot replace Fastlane for Android.
Option D — Codemagic
Weighted score: 3.72 / 5.0 — DISMISSED. Valid for Flutter projects but adds separate vendor dependency. GitHub Actions + Fastlane provides equivalent capability at lower per-project cost at Ascendion engagement volume.
Decision
Fastlane + GitHub Actions is mandatory for all Ascendion mobile projects. The pipeline runs the six stages below, with the quality gates enforced as blocking conditions before merge and before production deploy.
Pipeline Architecture
| Stage |
Trigger |
Android |
iOS |
Time |
| Validate |
Every PR |
Detekt lint, unit tests (90% gate), snapshot diff, security scan |
SwiftLint, unit tests (90% gate), snapshot diff |
~8 min |
| Build |
Merge to main |
Gradle assembleRelease, R8 full mode |
Xcode Archive, Release config |
~15 min |
| Sign |
Post-build |
Keystore decode from base64 secret |
fastlane match, appstore profile |
~3 min |
| Distribute |
Post-sign |
Firebase App Distribution, Play internal track |
TestFlight internal |
~5 min |
| QA Gate |
Manual |
GitHub Environment protection rule |
GitHub Environment protection rule |
24-48hr |
| Release |
Post-QA |
Play staged rollout 1%→5%→20%→100% |
App Store review submission |
~10 min |
Mandatory Quality Gates
| Gate |
Tool |
Threshold |
Failure Action |
| Use Case Coverage |
JaCoCo / Xcode Coverage |
> 90% |
Block PR merge |
| ViewModel Coverage |
JaCoCo / Xcode Coverage |
> 80% |
Block PR merge |
| Snapshot Test Diff |
Paparazzi / iOSSnapshot |
Zero pixel regressions |
Block PR merge |
| Lint and Static Analysis |
Detekt / SwiftLint |
Zero error-level violations |
Block PR merge |
| Dependency CVE Scan |
OWASP Dependency Check |
No P1/P2 CVEs |
Block merge, alert SA |
| QA Sign-off |
GitHub Environment Protection |
QA Lead approval |
Block production deploy |
Trade-off Analysis
| Trade-off Accepted |
Consequence |
Mitigation |
| macOS runners cost ~10× Linux |
iOS build minutes dominate pipeline cost |
Matrix strategy runs Android on Linux; macOS minutes confined to the iOS build/sign jobs only |
| Mandatory QA gate adds 24–48hr latency |
Production release is not fully automated |
GitHub Environment Protection makes the gate explicit and auditable; human QA catches what automated tests miss |
| fastlane Match requires a shared encrypted certificate repo |
Coordination + MATCH_PASSWORD secret management overhead |
Certificates auto-renew 30 days before expiry; any CI machine or developer with the passphrase can sign |
Implementation Guidance
iOS Code Signing — fastlane Match (Mandatory)
All certificates and provisioning profiles stored encrypted in a dedicated private Git repository. Every CI machine and developer clones fresh using MATCH_PASSWORD encrypted secret. Certificate rotation: Match regenerates automatically 30 days before expiry. Eliminates the certificate-locked-to-one-Mac failure mode permanently.
Android Code Signing — Keystore via GitHub Secrets (Mandatory)
Release Keystore encoded as base64 stored as KEYSTORE_B64 encrypted secret. CI decodes to temporary file, configures Gradle signingConfigs, deletes after signing. Enrolled in Google Play App Signing — Google manages distribution key.
Pipeline Setup Steps
- Configure GitHub Actions matrix to run Android (ubuntu-latest) and iOS (macos-latest) in parallel
- Initialise fastlane Match against a dedicated encrypted certificate repository; store MATCH_PASSWORD as an encrypted secret
- Encode the Android release keystore as base64 and store as KEYSTORE_B64 encrypted secret; enrol in Play App Signing
- Wire the six quality gates as blocking checks before merge
- Configure GitHub Environment Protection requiring QA Lead approval before the production environment
- Configure staged rollout via fastlane supply (Play) and fastlane deliver (App Store)
Compliance Checkpoints
| Checkpoint |
Trigger |
Owner |
SLA |
| Pipeline Architecture Review |
New mobile project setup |
Platform Engineer |
Before first release |
| Code Signing Setup Review |
Project initialisation |
Mobile DevOps Lead |
Before first build |
| Dependency CVE Gate |
Every CI build — automated |
CI Pipeline |
Real-time, blocks on P1/P2 |
| QA Sign-off Gate |
Every production release |
QA Lead |
Blocks production deploy |
| Coverage Gate |
Every PR — automated |
CI Pipeline |
Real-time |
| Reference |
Title |
Relationship |
| ADR-MOB-001 |
Mobile Architecture Pattern |
Test coverage gates reference standards from this ADR |
| ADR-MOB-002 |
Mobile Platform Selection |
Platform determines Android vs iOS pipeline configuration |
| ADR-SEC-011 |
Mobile Security Controls |
Dependency CVE scanning gate references security standards |
Flowchart
%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%%
flowchart TD
GIT["<b>git push to main</b><br/>Trunk-based · Feature branch max 2 days"]
subgraph VALIDATE["Stage 1 — Validate · < 8 min · Every PR"]
direction LR
LINT["<b>Lint · Static Analysis</b><br/>Detekt · SwiftLint<br/>Zero violations — blocks merge"]
UNIT_CI["<b>Unit Tests</b><br/>Use Cases ≥ 90%<br/>ViewModels ≥ 80%"]
SNAP_CI["<b>Snapshot Diff</b><br/>Zero pixel regressions<br/>All themes · Dynamic Type"]
SEC_CI["<b>Security Scan</b><br/>OWASP Dependency Check<br/>GitLeaks · Blocks P1/P2 CVE"]
end
subgraph BUILD["Stage 2 — Parallel Build · Merge to Main"]
direction LR
AND_BUILD["<b>Android — ubuntu-latest</b><br/>Gradle assembleRelease<br/>R8 Full Mode · Baseline Profiles"]
IOS_BUILD["<b>iOS — macos-latest</b><br/>Xcode Archive · Release<br/>dSYM for Crashlytics"]
end
subgraph SIGN["Stage 3 — Code Signing"]
direction LR
AND_SIGN["<b>Android Keystore</b><br/>Base64 GitHub Secret<br/>Play App Signing enrolled"]
IOS_SIGN["<b>fastlane Match</b><br/>Encrypted Git repo<br/>Auto-renews 30d before expiry"]
end
subgraph DIST["Stage 4 — QA Distribution"]
direction LR
FIREBASE["<b>Firebase App Distribution</b><br/>Android internal builds"]
TF["<b>TestFlight Internal</b><br/>iOS internal testing"]
end
QA_CI{"<b>Stage 5 — QA Gate</b><br/>GitHub Environment Protection<br/>QA Lead approval required"}
subgraph RELEASE["Stage 6 — Production Release"]
direction LR
PLAY["<b>Google Play</b><br/>fastlane supply<br/>1%→5%→25%→50%→100%"]
APPSTORE["<b>App Store</b><br/>fastlane deliver<br/>Expedited review available"]
end
GIT --> VALIDATE --> BUILD --> SIGN --> DIST --> QA_CI
QA_CI -->|"Approved"| RELEASE
QA_CI -->|"Rejected"| GIT
style GIT fill:#1D4ED8,stroke:#1e3a5f,color:#ffffff
style VALIDATE fill:#DCFCE7,stroke:#16A34A,stroke-width:2px
style BUILD fill:#DBEAFE,stroke:#2563EB,stroke-width:2px
style SIGN fill:#FEE2E2,stroke:#DC2626,stroke-width:2px
style DIST fill:#FEF9C3,stroke:#CA8A04,stroke-width:2px
style QA_CI fill:#FEF3C7,stroke:#D97706,stroke-width:2px
style RELEASE fill:#F3E8FF,stroke:#7C3AED,stroke-width:2px
style LINT fill:#BBF7D0,stroke:#16A34A,color:#14532D
style UNIT_CI fill:#BBF7D0,stroke:#16A34A,color:#14532D
style SNAP_CI fill:#BBF7D0,stroke:#16A34A,color:#14532D
style SEC_CI fill:#BBF7D0,stroke:#16A34A,color:#14532D
style AND_BUILD fill:#BFDBFE,stroke:#2563EB,color:#1e3a5f
style IOS_BUILD fill:#BFDBFE,stroke:#2563EB,color:#1e3a5f
style AND_SIGN fill:#FCA5A5,stroke:#DC2626,color:#7f1d1d
style IOS_SIGN fill:#FCA5A5,stroke:#DC2626,color:#7f1d1d
style FIREBASE fill:#FDE68A,stroke:#CA8A04,color:#713f12
style TF fill:#FDE68A,stroke:#CA8A04,color:#713f12
style PLAY fill:#E9D5FF,stroke:#7C3AED,color:#4C1D95
style APPSTORE fill:#E9D5FF,stroke:#7C3AED,color:#4C1D95
References
- Fastlane Documentation. docs.fastlane.tools
- GitHub Actions — Environments. docs.github.com/en/actions/deployment
- Google — Play Console Staged Rollouts. support.google.com/googleplay/android-developer
- Forsgren et al. — Accelerate. IT Revolution, 2018.
- OWASP — Dependency Check. owasp.org/www-project-dependency-check
⚠ Manual Code Signing — Engineer manually downloading provisioning profiles, building locally, uploading to TestFlight. Undocumented, unreproducible, fails when that engineer is unavailable.
CORRECT: fastlane Match. All signing materials in encrypted repository. Any CI machine or developer with the passphrase can sign and release.
⚠ No QA Gate Before Production — Pipeline automatically promotes to production after passing automated tests. Automated tests catch regressions; human QA catches usability and visual issues automated tests miss.
CORRECT: GitHub Environment Protection Rule requiring QA Lead approval before any production release.
End of Document
Maintained by
Architecture Council
Review Cadence
Annually or upon any
Tier 2 architectural change
Change Requests
Submitted as RFC to the
Architecture Council for review
Version History
Maintained in version
control alongside this document