Automating Builds and Code Signing with Fastlane (Revamped Guide)

Table of Contents
Big thanks to our contributors those make our blogs possible.

Our growing community of contributors bring their unique insights from around the world to power our blog. 

Introduction

Mobile teams often waste hours wrestling with build scripts, provisioning profiles, and signing certificates—instead of writing features. Fastlane streamlines every step of your iOS and Android release pipeline:

  • Certificate & Profile management
  • Automated builds (Xcode/Gradle)
  • Code signing (match & keystores)
  • Test execution (unit, UI)
  • App Store / Play Store deployment

With a few configuration files and commands, Fastlane turns fragile manual processes into repeatable, secure, CI‑friendly lanes—so you can ship faster, with consistency and confidence.

Table of Contents

  1. Why Automate Builds & Signing
  2. Fastlane Setup & Initialization
  3. iOS Code Signing with match
  4. Defining iOS Lanes in the Fastfile
  5. Android Keystore Management
  6. Defining Android Lanes
  7. CI/CD Integration Examples
  8. Best Practices & Folder Structure
  9. Troubleshooting & Tips
  10. Conclusion

Why Automate Builds & Signing

ChallengePain PointFastlane Solution
Manual certificate installsInconsistent profiles, developer-specific setupsmatch centralizes in Git repo
Complex Xcode settingsFrequent code signing errorsgym auto-configures signing
Gradle keystore configsHard to share secrets between team/CIGPG‑encrypted keystore + env vars
Non-interactive CI requirementsBuilds break without UI promptsFully scriptable lanes
Ad-hoc distributionManual TestFlight/App Store uploadspilot and supply automate uploads

Fastlane Setup & Initialization

Prerequisites

  • Ruby ≥ 2.6 (rbenv, RVM, or system Ruby)
  • Xcode CLI (xcodebuild) for iOS
  • Android SDK & Java (with sdkmanager, gradle in PATH)

Install Fastlane

bashCopyEdit# macOS (system Ruby)
sudo gem install fastlane -NV

# Linux (user install)
gem install fastlane --user-install

Tip: Pin Fastlane in a Gemfile for version consistency:

rubyCopyEditsource 'https://rubygems.org'
gem 'fastlane', '~> 2.250'

Initialize Fastlane

bashCopyEdit# iOS
cd ios
fastlane init

# Android
cd ../android      # or your Android project root
fastlane init

Choose a template (beta App Store, Google Play, manual) or “Skip” to create a blank Fastfile.

iOS Code Signing with match

Fastlane’s match stores your certificates and provisioning profiles in a private Git repository, encrypted with a passphrase. All machines (dev & CI) pull the same assets.

Matchfile Configuration

rubyCopyEditgit_url("[email protected]:your_org/ios-certs.git")
type("appstore")             # [development, appstore, adhoc, enterprise]
app_identifier(["com.example.app"])  # Your bundle IDs
username("[email protected]")    # Apple ID with access
team_id("XXXXXXXXXX")         # Apple Developer Team ID
readonly(false)               # true for CI read-only access

Match Commands

CommandDescription
fastlane match developmentGenerate & sync development certificates/profiles
fastlane match appstoreGenerate & sync App Store certificates/profiles
fastlane match nukeRevoke all certificates & profiles (use with care)

Security: Store MATCH_PASSWORD in your CI’s secrets to decrypt Git repo.

Defining iOS Lanes in the Fastfile

A lane is a sequence of Fastlane actions:

rubyCopyEditdefault_platform(:ios)

platform :ios do
  before_all do
    match(type: "development")  # pull dev certs for tests
    match(type: "appstore")     # pull appstore certs for release
  end

  desc "Run tests and lint"
  lane :ci do
    scan(
      scheme: "MyAppTests",
      device: "iPhone 14",
      clean: true,
      code_coverage: true
    )
    swiftlint(strict: true)
  end

  desc "Build, archive, and upload to TestFlight"
  lane :beta do
    ci
    gym(
      workspace: "MyApp.xcworkspace",
      scheme: "MyApp",
      configuration: "Release",
      output_directory: "build/ios",
      output_name: "MyApp.ipa",
      export_method: "app-store"
    )
    pilot(
      skip_submission: true,
      skip_waiting_for_build_processing: true
    )
  end

  desc "Submit existing build for App Store review"
  lane :release do
    deliver(force: true)
  end
end
  • scan: runs tests on simulator
  • gym: builds & archives .ipa
  • pilot: uploads to TestFlight
  • deliver: submits to App Store

Android Keystore Management

Android apps require a keystore for signing. Fastlane can decrypt an encrypted keystore, then invoke Gradle with the right properties.

Encrypt Your Keystore

bashCopyEdit# Interactive GPG symmetric encryption
gpg --symmetric --cipher-algo AES256 myapp.keystore
# Produces myapp.keystore.gpg

Store myapp.keystore.gpg in your repo, and add DECRYPT_PASSWORD to your CI secrets.

Matchfile for Android (optional)

Fastlane supports match for Android using cert store, but most teams use manual GPG encryption.

Defining Android Lanes

Edit android/fastlane/Fastfile:

rubyCopyEditdefault_platform(:android)

platform :android do
  before_all do
    # Decrypt keystore
    sh "echo $DECRYPT_PASSWORD | gpg --batch --yes --passphrase-fd 0 -o keystore.jks -d myapp.keystore.gpg"
  end

  desc "Run unit tests and lint"
  lane :ci do
    gradle(task: "clean")
    gradle(task: "testDebugUnitTest")
    gradle(task: "lint")
  end

  desc "Build and deploy to Beta track on Google Play"
  lane :beta do
    ci
    gradle(
      task: "assembleRelease",
      build_type: "Release",
      properties: {
        "android.injected.signing.store.file" => "keystore.jks",
        "android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
        "android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
        "android.injected.signing.key.password" => ENV["KEY_PASSWORD"]
      }
    )
    supply(
      track: "beta",
      json_key_data: ENV["PLAY_STORE_JSON_KEY"],
      apk: "app/build/outputs/apk/release/app-release.apk"
    )
  end

  desc "Promote Beta build to Production"
  lane :release do
    supply(
      track: "production",
      json_key_data: ENV["PLAY_STORE_JSON_KEY"],
      apk: "app/build/outputs/apk/release/app-release.apk"
    )
  end
end
  • supply: uploads to Play Store
  • Properties: override Gradle signing config at runtime

CI/CD Integration Examples

GitHub Actions Workflow

yamlCopyEditname: Mobile CI/CD
on:
  push:
    branches: [ main ]

env:
  # Common secrets names
  MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
  APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}

jobs:
  ios:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '2.7'
      - name: Install Fastlane
        run: gem install fastlane -NV
      - name: iOS CI & Build
        run: fastlane ios beta
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}

  android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '2.7'
      - name: Install Fastlane
        run: gem install fastlane -NV
      - name: Decrypt Keystore
        run: |
          echo $DECRYPT_PASSWORD | gpg --batch --yes --passphrase-fd 0 -o keystore.jks -d android/myapp.keystore.gpg
        env:
          DECRYPT_PASSWORD: ${{ secrets.DECRYPT_PASSWORD }}
      - name: Android CI & Build
        run: fastlane android beta
        env:
          KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
          KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
          PLAY_STORE_JSON_KEY: ${{ secrets.PLAY_STORE_JSON_KEY }}

GitLab CI Example

yamlCopyEditstages:
  - test
  - build

iOS:
  image: macos:latest
  stage: build
  before_script:
    - gem install fastlane -NV
  script:
    - fastlane ios beta
  only:
    - main

Android:
  image: ruby:2.7
  stage: build
  before_script:
    - gem install fastlane -NV
    - echo $DECRYPT_PASSWORD | gpg --batch --yes --passphrase-fd 0 -o keystore.jks -d android/myapp.keystore.gpg
  script:
    - fastlane android beta
  only:
    - main

Best Practices & Folder Structure

bashCopyEditproject-root/
├── ios/
│   └── fastlane/
│       ├── Fastfile
│       └── Matchfile
├── android/
│   └── fastlane/
│       └── Fastfile
├── Gemfile           # pin fastlane version
├── myapp.keystore.gpg
└── README.md         # FASTLANE.md with lane docs
  • Gemfile: lock Fastlane, other Ruby gems.
  • FASTLANE.md: document each lane, required env vars, how to run locally.
  • Encrypted Assets: keysteores & certs under version control only as encrypted files.
  • Clear Lane Naming: ios ci, ios beta, android ci, android beta, android release.

Troubleshooting & Tips

SymptomCause & Solution
Fastlane match fails with Git errorEnsure MATCH_PASSWORD is correct and SSH keys/cert access is set up.
Xcode code signing errorsVerify app_identifier, team_id, and provisioning profile matches.
Gradle signing not appliedUse android.injected.signing.* properties or configure build.gradle.
CI build hanging on promptPass --readonly to match or set FASTLANE_DISABLE_COLORS=1.
Pilot upload times outUse skip_waiting_for_build_processing: true to proceed.
Supply upload auth errorEnsure PLAY_STORE_JSON_KEY is valid JSON, no newlines lost in CI v

Conclusion

Fastlane transforms complex mobile build and signing processes into simple, automated lanes. By adopting:

  • match for centralized iOS certificate & profile management
  • GPG‑encrypted keystores for Android in Git
  • Gym/Gradle actions for reproducible builds
  • Pilot/Supply for upload automation
  • CI integration in GitHub Actions or GitLab

you’ll eliminate manual errors, empower continuous delivery, and let your team focus on code rather than provisioning. Start by running fastlane init, configuring your Fastfile, and automating one lane at a time—then watch your mobile release cycle shrink from hours to minutes. Happy shipping!

Let's connect on TikTok

Join our newsletter to stay updated

Sydney Based Software Solutions Professional who is crafting exceptional systems and applications to solve a diverse range of problems for the past 10 years.

Share the Post

Related Posts