Introduction
Automating build, test, and deployment pipelines is essential for modern mobile teams to release high‑quality apps with speed and reliability. GitHub Actions provides a native, declarative CI/CD solution directly within your repo—enabling you to:
- Build on macOS (iOS) and Ubuntu (Android) runners
- Test unit, UI, and integration scenarios across simulator/emulator matrices
- Sign code securely using encrypted certs, profiles, and keystores
- Cache dependencies (CocoaPods, Gradle, npm) for fast iterative runs
- Deploy to TestFlight, App Store, Google Play, or internal distribution channels
- Notify teams of build status via Slack, email, or other integrations

This guide walks you through designing a comprehensive mobile CI/CD pipeline in GitHub Actions—covering workflow structure, runner selection, key stages, secure signing, artifact management, deployment automation, and best practices for maintainable, modular workflows.
Table of Contents
- Pipeline Stages Overview
- Runner Selection & Matrices
- Android Workflow Example
- iOS Workflow Example
- Secure Code Signing
- Deployment Automation
- Notifications & Monitoring
- Best Practices & Modularization
- Folder Structure & Reusable Actions
- Conclusion
Pipeline Stages Overview
Every mobile CI/CD pipeline typically follows these stages:
| Stage | Purpose |
|---|---|
| Checkout & Setup | Fetch code, set up runtime (JDK, Ruby, Node, SDKs) |
| Dependency Installation | Install CocoaPods, Gradle dependencies, npm modules |
| Build & Compile | Produce debug/release APKs, IPAs |
| Static Analysis & Lint | SwiftLint, ESLint, detekt, etc. |
| Unit & UI Tests | Run tests on simulators/emulators, real devices if available |
| Code Signing & Provisioning | Decrypt and install profiles, keystores, certificates |
| Artifact Archival | Upload build artifacts for distribution or debugging |
| Deployment | Fastlane pilot/supply, custom scripts to stores or internal |
Runner Selection & Matrices
| Platform | Runner | Use Cases |
|---|---|---|
| iOS | macos-latest | Xcode build, CocoaPods, SwiftLint |
| Android | ubuntu-latest | Gradle build, Android emulator |
| All Platforms | self-hosted macOS | Custom Xcode versions, private devices |
Matrix Builds allow parallelizing across versions:
yamlCopyEditstrategy:
matrix:
api-level: [29, 30, 31]
jdk: [11, 17]
Android Workflow Example
yamlCopyEditname: Android CI/CD
on:
push:
branches: [ main ]
pull_request:
jobs:
build-test:
runs-on: ubuntu-latest
strategy:
matrix:
jdk: [ '11', '17' ]
api-level: [ 29, 30, 31 ]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup JDK ${{ matrix.jdk }}
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: ${{ matrix.jdk }}
- name: Cache Gradle
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ matrix.jdk }}-${{ matrix.api-level }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
- name: Accept Android SDK Licenses
run: yes | sdkmanager --licenses
- name: Build Debug APK
run: ./gradlew assembleDebug
- name: Run Unit Tests
run: ./gradlew testDebugUnitTest
- name: Run Instrumented Tests
run: |
# Create and start emulator
echo "no" | avdmanager create avd -n test -k "system-images;android-${{ matrix.api-level }};google_apis;x86_64"
emulator -avd test -no-window -no-audio &
adb wait-for-device
./gradlew connectedAndroidTest
- name: Archive Debug APK
uses: actions/upload-artifact@v3
with:
name: app-debug.apk
path: app/build/outputs/apk/debug/app-debug.apk
Key Points:
- Matrix for JDK and API levels ensures broad coverage
- Gradle Cache reduces build time significantly
- Emulator Setup headless for instrumented tests
iOS Workflow Example

yamlCopyEditname: iOS CI/CD
on:
push:
branches: [ main ]
pull_request:
jobs:
build-test:
runs-on: macos-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Setup Ruby & Bundler
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.0'
bundler-cache: true
- name: Install Dependencies
run: |
cd ios
bundle install
bundle exec pod install
- name: Cache CocoaPods
uses: actions/cache@v3
with:
path: ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
- name: Run SwiftLint
run: bundle exec swiftlint lint
- name: Build & Test on Simulator
run: |
xcodebuild clean test \
-workspace ios/MyApp.xcworkspace \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 14,OS=16.0' \
-enableCodeCoverage YES
- name: Archive Development IPA
run: |
bundle exec fastlane gym \
workspace:"ios/MyApp.xcworkspace" \
scheme:"MyApp" \
export_method:"development" \
output_directory:"build/ios" \
output_name:"MyApp.ipa"
- name: Upload IPA
uses: actions/upload-artifact@v3
with:
name: MyApp.ipa
path: build/ios/MyApp.ipa
Highlights:
- Bundler Cache speeds up gem installs
- CocoaPods Cache saves time on
pod install - SwiftLint enforces style before build
- Fastlane gym for consistent IPA packaging
Secure Code Signing
| Platform | Tool/Action | Description |
|---|---|---|
| iOS | fastlane match / OpenSSL | Centralized profiles via Git or encrypted p12 import |
| Android | GPG + Gradle properties | Decrypt keystore.jks.gpg and pass signing props |
iOS: Decrypt & Install
yamlCopyEdit- name: Decrypt iOS Certificates
run: |
echo $MATCH_PASSWORD | fastlane match appstore --readonly
or manual:
yamlCopyEdit- name: Decrypt p12 & profiles
run: |
openssl aes-256-cbc -K ${{ secrets.KEY }} -iv ${{ secrets.IV }} \
-in ios/certs/distribution.p12.enc -out dist.p12 -d
security import dist.p12 -k ~/Library/Keychains/login.keychain -P ""
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp ios/certs/profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
Android: Decrypt Keystore

yamlCopyEdit- name: Decrypt Android Keystore
run: |
echo $DECRYPT_PASSWORD | gpg --batch --yes --passphrase-fd 0 \
-o android/keystore.jks -d android/myapp.keystore.jks.gpg
Use android.injected.signing.* Gradle properties in your build step.
Deployment Automation
Android: Play Store via Fastlane supply
yamlCopyEdit- name: Deploy to Play Store (Internal)
run: fastlane supply \
track: internal \
json_key_data: ${{ secrets.PLAY_STORE_JSON_KEY }} \
apk: app/build/outputs/apk/release/app-release.apk
iOS: TestFlight via Fastlane pilot
yamlCopyEdit- name: Upload to TestFlight
run: fastlane pilot upload \
ipa:build/ios/MyApp.ipa \
skip_waiting_for_build_processing:true
Manual Approvals: Use workflow_dispatch and require GitHub environment approvals for production lanes.
Notifications & Monitoring
Integrate Slack, Teams, or email notifications for pipeline status:
yamlCopyEdit- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,commit,author
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
Monitor build times, flake rates, and deployment health via actionable dashboard or GitHub checks.
Best Practices & Modularization
- Split Workflows: Separate Android and iOS into distinct YAML files for clarity.
- Composite Actions: Encapsulate common steps (checkout, setup, cache) for reuse across multiple repos.
- Environment Variables: Centralize all secrets in GitHub Secrets, prefixing by platform (IOS_, ANDROID_).
- Fail Fast: Run lint and unit tests before lengthy emulator/simulator or signing steps.
- Version Control Artifacts: Upload IPA/APK and logs as workflow artifacts for diagnostics.
- Documentation: Maintain a
CI.mdexplaining workflow setup, secret requirements, and how to run locally viaact.
Folder Structure & Reusable Actions

arduinoCopyEdit.github/
└── workflows/
├── android-ci.yml
├── ios-ci.yml
└── deploy.yml
.github/actions/
├── setup-checkout/
│ └── action.yml # Checkout + repo setup
└── cache-setup/
└── action.yml # CocoaPods/Gradle cache logic
fastlane/
├── ios/
│ ├── Fastfile
│ └── Matchfile
└── android/
└── Fastfile
- Custom Actions let you share logic (cache keys, emulator setup) across projects.
- Deploy Workflow can be manually triggered (
workflow_dispatch) and gated by environment approvals.
Conclusion
By leveraging GitHub Actions and Fastlane, you can build a robust, end‑to‑end CI/CD pipeline for both iOS and Android:
- Automated Builds across simulator/emulator matrices
- Static Analysis and Unit/UI Tests early in the pipeline
- Secure Code Signing with match/GPG decryption
- Artifact Archival for debugs and audits
- Automated Deployment to TestFlight and Play Store
- Notifications & Approvals to keep stakeholders informed
Start by crafting simple build-test workflows, then incrementally add signing, deployment, and notifications. With modular, reusable actions and rigorous secret management, your mobile teams will ship features faster, catch regressions early, and maintain confidence in every release—across platforms, on every device.























































































































































































































































































































































































































































































































































































































































































