ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

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

TechniqueiOS ImpactAndroid Impact
Incremental builds40-60% faster30-50% faster
Module caching50%+ faster (SPM)40%+ faster (Gradle)
Parallel testing3-4x faster (multiple sims)2-3x faster (sharding)
Build cache (CI)30-50% faster40-60% faster
ccache/sccache50-70% faster (C/C++ deps)N/A

Anti-Patterns

Anti-PatternConsequenceFix
Manual code signing”Works on my machine”Fastlane match (iOS), CI keystore (Android)
No beta distributionOnly test on dev devicesTestFlight + Play Store Internal Track
Build everything on every PR30+ min CIBuild check only, full build on merge
No build cachingSlow CI, wasted computeCache derived data (iOS), Gradle cache (Android)
Manual App Store submissionError-prone, slowFastlane deliver / supply

Mobile CI/CD pipelines are more complex than server-side. Invest in automation early — the time savings compound with every release.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →