Skip to main content
โšก Calmops

Mobile CI/CD: Fastlane, GitHub Actions, and App Store Deployment

Mobile CI/CD: Fastlane, GitHub Actions, and App Store Deployment

TL;DR: This guide covers mobile CI/CD setup. Learn Fastlane, GitHub Actions, test automation, and deploying to App Store and Play Store.


Introduction

Mobile app development requires efficient build and deployment pipelines. With apps needing to target iOS and Android, often across multiple environments (development, staging, production), manual builds are unsustainable. A robust CI/CD pipeline automates testing, building, and deployment, reducing errors and accelerating release cycles.

This guide covers the essential components of mobile CI/CD: Fastlane for automation, GitHub Actions for CI/CD orchestration, testing integration, and App Store deployment. Whether you’re deploying to TestFlight, Google Play, or the App Store, these tools streamline the entire process.

Fastlane: Mobile Automation

Fastlane is the industry standard for mobile build automation. It provides tools for building, testing, and deploying iOS and Android apps. Fastlane lanes define repeatable workflows that can be executed from the command line.

Installation

# Install Fastlane via Ruby
sudo gem install fastlane

# Or via Homebrew (macOS)
brew install fastlane

# Navigate to your project and initialize
fastlane init

The initialization process creates a fastlane directory with a Fastfile where you’ll define your automation lanes.

Fastfile Structure

default_platform(:ios)

platform :ios do
  desc "Build and test the app"
  lane :build do
    # Run tests
    scan
    
    # Build the app
    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp"
    )
  end

  desc "Run unit tests"
  lane :test do
    scan(
      scheme: "MyApp",
      clean: true
    )
  end

  desc "Deploy to TestFlight"
  lane :deploy_testflight do
    # Get the latest build number
    increment_build_number
    
    # Build
    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp"
    )
    
    # Upload to TestFlight
    upload_to_testflight(
      skip_waiting_for_build_processing: true,
      distribute_external: true,
      groups: ["Testers"]
    )
  end

  desc "Deploy to App Store"
  lane :deploy_appstore do
    # Ensure code signing is configured
    match(type: "appstore")
    
    # Build and upload
    build_app(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp"
    )
    
    # Submit for review
    deliver(
      skip_screenshots: true,
      skip_metadata: true,
      force: true
    )
  end
end

Android Configuration

platform :android do
  desc "Build Android debug APK"
  lane :build do
    gradle(
      task: "assembleDebug"
    )
  end

  desc "Deploy to Google Play (Internal)"
  lane :deploy_playstore do
    gradle(
      task: "assembleRelease"
    )
    
    supply(
      track: "internal",
      apk: "app/build/outputs/apk/release/app-release.apk"
    )
  end
end

Match for Code Signing

Fastlane Match manages certificates and provisioning profiles:

lane :setup do
  match(
    type: "development",
    readonly: true
  )
end

GitHub Actions CI/CD

GitHub Actions provides cloud-based CI/CD with generous free tiers for open source and reasonable pricing for private repositories.

Basic iOS CI Workflow

name: Mobile CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    name: Run Tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
      
      - name: Run unit tests
        run: npm test
      
      - name: Run linting
        run: npm run lint

  build-ios:
    name: Build iOS
    needs: test
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Cache Ruby gems
        uses: actions/cache@v4
        with:
          path: vendor/bundle
          key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
          
      - name: Install Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
          bundler-cache: true
          
      - name: Install dependencies
        run: |
          cd ios
          bundle install
          
      - name: Build iOS
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
        run: bundle exec fastlane build

  build-android:
    name: Build Android
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Java
        uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '17'
          
      - name: Setup Android SDK
        uses: android-actions/setup-android@v3
        
      - name: Build Android
        run: |
          cd android
          ./gradlew assembleDebug

Scheduled Builds and Releases

name: Release Pipeline

on:
  push:
    tags:
      - 'v*'

jobs:
  release:
    name: Create Release
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        
      - name: Install dependencies
        run: bundle install
        
      - name: Build and Deploy
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
        run: bundle exec fastlane release
      
      - name: Create GitHub Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ github.ref }}
          release_name: Release ${{ github.ref }}
          draft: true

Test Automation

Unit Testing in CI

- name: Run tests
  run: |
    npm test -- --coverage
    
- name: Upload coverage
  uses: codecov/codecov-action@v4
  with:
    token: ${{ secrets.CODECOV_TOKEN }}

Snapshot Testing

lane :snapshot do
  snapshot(
    scheme: "MyApp",
    devices: ["iPhone 14", "iPhone SE", "iPad Pro 11"]
  )
end

App Store Deployment

Version Management

# Auto-increment build number
lane :prepare_release do
  # Get latest build from TestFlight
  latest_build = latest_testflight_build_number
  new_build = latest_build + 1
  
  increment_build_number(
    build_number: new_build
  )
  
  # Update version if needed
  increment_version_number(
    version_number: "1.2.0"
  )
end

Play Store Deployment

lane :deploy_playstore do
  gradle(
    task: "assembleRelease",
    build_type: "Release"
  )
  
  supply(
    track: "internal",
    apk: "app/build/outputs/apk/release/app-release-unsigned.apk",
    json_key_data: ENV["PLAY_STORE_JSON_KEY"]
  )
end

Best Practices

Secrets Management

Never commit secrets to GitHub. Use:

  • GitHub Secrets for sensitive values
  • Fastlane Match for certificates
  • Environment files loaded at runtime

Caching

- name: Cache Node modules
  uses: actions/cache@v4
  with:
    path: node_modules
    key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}

Parallel Jobs

Run independent jobs in parallel to reduce total CI time:

jobs:
  test-ios:
    runs-on: macos-latest
    steps: [...]
    
  test-android:
    runs-on: ubuntu-latest
    steps: [...]

Conclusion

Mobile CI/CD requires:

  1. Fastlane - Automation for builds and deployment
  2. GitHub Actions - Cloud CI/CD orchestration
  3. Test automation - Quality assurance with every change
  4. App Store deployment - Automated release to stores

Implementing these tools transforms mobile development from manual builds to automated, reliable releases. Start with basic builds, add testing, then expand to full deployment automation.

Modern Mobile CI/CD in 2026

AI-Powered Testing

# AI-assisted visual testing
- name: Percy Visual Tests
  uses: percy/cli@latest
  with:
    files: ['**/*.png']
    ignore: ['**/android/**']

# AI test generation
- name: AI Test Generation
  uses: appmap/appmap-agent@v1
  with:
    - name: Generate AI tests
      run: |
        # Use AI to generate test cases
        appmap record -- open MyApp.app
        appmap test generate --ai

Mobile-Specific GitHub Actions

# Matrix testing across devices
jobs:
  test:
    strategy:
      matrix:
        device: ["iPhone 15", "iPhone SE", "iPad Pro"]
        os-version: ["17.0", "16.0"]
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run XCTests
        run: xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=${{ matrix.device }},OS=${{ matrix.os-version }}'

Environment-Specific Deployments

# Fastlane environment configuration
lane :deploy do
  environment = ENV['DEPLOY_ENV']
  
  case environment
  when 'development'
    build_app(scheme: 'MyApp-Dev')
    deploy_to_testflight(skip_waiting_for_build_processing: true)
  when 'staging'
    build_app(scheme: 'MyApp-Staging')
    deploy_to_testflight
  when 'production'
    match(type: 'appstore')
    build_app(scheme: 'MyApp')
    upload_to_app_store
  end
end

App Store Connect API Integration

# Using App Store Connect API for metadata
lane :update_metadata do
  app_store_connect_api_key
  
  # Update app metadata
  deliver(
    metadata_path: "./metadata",
    app_identifier: "com.myapp.app",
    skip_screenshots: true,
    overwrite_screenshots: false
  )
end

Firebase App Distribution

# Deploy to Firebase App Distribution
lane :deploy_firebase do
  gradle(task: "assembleRelease")
  
  firebase_app_distribution(
    app: "1:123456789:android:abc123",
    groups: "testers",
    firebase_token: ENV["FIREBASE_TOKEN"],
    release_notes: "New features in this release!"
  )
end

Codemagic Alternative

# codemagic.yaml for alternative CI/CD
workflows:
  ios-workflow:
    name: iOS Workflow
    max_build_duration: 60
    environment:
      flutter: stable
      xcode: latest
      cocoapods: default
    scripts:
      - name: Set up code signing
        script: |
          keychain initialize
          app-store-connect fetch-signing-files "com.myapp" --type IOS_APP_STORE
          keychain add-certificates
      - name: Flutter build
        script: flutter build ios --release
      - name: Deploy to App Store
        script: |
          app-store-connect publish \
            --path build/ios/ipa/*.ipa

RevenueCat Integration

# Configure RevenueCat in CI
lane :configure_revenuecat do
  # Set up RevenueCat API key
  ENV["REVENUECAT_API_KEY"] = "sk_live_xxx"
  
  # Create offering
  api_key = ENV["REVENUECAT_API_KEY"]
  # RevenueCat API calls for managing offerings
end

Build Metrics and Monitoring

# Track build times and metrics
- name: Build Metrics
  run: |
    echo "Build Time: ${{ github.event.inputs.build_time }}"
    echo "Build Status: ${{ job.status }}"
    
- name: Send to Datadog
  uses: DataDog/datadog-metric-action@v1
  with:
    api_key: ${{ secrets.DATADOG_API_KEY }}
    metrics: |
      - metric: mobile.build.time
        type: gauge
        value: ${{ github.event.inputs.build_time }}
        tags: ["env:production"]

Comments