Introduction
Fastlane is the essential open-source tool for automating mobile workflows in 2026, handling builds, tests, screenshots, and deployments without manual intervention. For senior devs, it goes beyond basic scripts with custom lanes, modular plugins, and native CI/CD integrations, shrinking release cycles from days to minutes. Imagine: a commit push triggers unit/UI tests, localized screenshot generation, signed builds, and uploads to TestFlight or Google Play—all with secret management and automated rollbacks.
This advanced tutorial targets pros: multi-platform setup (iOS/Android), performance tweaks (caching certs/provisioning), and CI scaling. You'll save 80% on release time, eliminate human errors (like expired profiles), and scale to 10+ builds/day. Ready to turn your deployments into a well-oiled machine? Let's follow a real project: 'MyMobileApp' with weekly betas and monthly prod releases.
Prerequisites
- macOS Ventura+ with Xcode 16+ (for iOS) and Android Studio Iguana+ (for Android).
- Accounts: Apple Developer Program ($99/year), Google Play Console ($25 one-time).
- Ruby 3.2+ and Bundler 2.4+ installed (
gem install bundler). - Existing project: Git repo with iOS Xcodeproj and Android app-level build.gradle.
- GitHub repo with secrets:
MATCH_PASSWORD,APPSTORE_CONNECT_KEY_ID,GOOGLE_SERVICE_ACCOUNT_JSON. - Knowledge: Basic Ruby, Fastlane actions (gym, fastlane/match).
Fastlane Installation and Initialization
#!/bin/bash
# Install Fastlane via Bundler (recommended for isolation)
gem install bundler
bundle init
# Add Fastlane and advanced plugins
cat > Gemfile << EOF
source "https://rubygems.org"
gem "fastlane"
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
EOF
bundle install
# Initialize for iOS and Android
fastlane init --ios
fastlane init --android
# Configure Appfile for iOS
cat > fastlane/Appfile << EOF
app_identifier "com.example.mymobileapp"
apple_id "votre@apple.id"
team_id "ABC123DEF4"
EOF
# Configure Appfile for Android
cat > ./android/fastlane/Appfile << EOF
package_name "com.example.mymobileapp"
json_key_file "path/to/google-service-account.json"
EOFThis script sets up an isolated environment with Bundler, avoiding gem conflicts. The init creates fastlane/ folders for iOS and ./android/fastlane/ for Android, generating skeleton Fastfiles. Appfiles centralize app identifiers, catching package/bundle ID mismatches early.
Certificate Setup with Match
Match centralizes profiles and certs in a private Git repo, making CI clones reproducible. For advanced use, set readonly: true in CI and generate locally. Pair it with 1Password or GitHub Secrets for automatic rotation.
Match Setup for Shared Certificates
storage_mode("git")
git_url("git@github.com:yourorg/fastlane-certs.git")
app_identifier(["com.example.mymobileapp", "com.example.mymobileapp.Staging"])
username("votre@apple.id")
team_id("ABC123DEF4")
readonly(true)
# Advanced: specific types
# Development certs auto-generated
# AdHoc for beta/TestFlight
# AppStore for productionThe Matchfile configures a dedicated Git repo for certs, supporting multi-environments (Staging/Prod). readonly(true) protects against accidental overwrites in CI. Pitfall: always commit/push after match init, or clones will fail with 'no profiles found'.
Gemfile with Advanced Plugins
source "https://rubygems.org"
gem "fastlane", "~> 2.220"
# Advanced plugins for screenshots, performance, and analytics
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
gem "fastlane-plugin-screenshot_timer"
gem "fastlane-plugin-match_custom"
gem "fastlane-plugin-appcenter_distribute"
# CI optimizations
gem "cocoapods"
gem "jazzy" # Auto docs
group :test do
gem "slather" # Coverage
end
# Lock versions for CI reproducibility
eval_gemfile "fastlane/Pluginfile"This Gemfile locks versions and adds plugins for screenshot timers (optimizes slow builds), custom Match, and AppCenter (TestFlight alternative). Pluginfile is auto-generated by bundle exec fastlane install_plugins. Avoid global gems: Bundler ensures isolation and GitHub Actions caching.
iOS Fastfile: Advanced Lanes
Progressive structure: Modular lanes for test → build → screenshots → deploy. Use before_all/after_all for shared setup/teardown, like CocoaPods caching.
Complete iOS Fastfile with Screenshots and Beta
default_platform(:ios)
platform :ios do
before_all do
# Shared setup: certs, pods
match(type: "appstore", readonly: true)
cocoapods
end
desc "Tests unitaires et UI"
lane :test do
scan(
scheme: "MyMobileApp",
devices: ["iPhone 15 Pro"],
clean: true
)
slather
end
desc "Générer screenshots localisés"
lane :screenshots do
snapshot(
locale: ["en-US", "fr-FR"],
dark_mode: true,
ios_multiplier: 3
)
frameit
end
desc "Beta TestFlight"
lane :beta do
increment_build_number(build_number: latest_testflight_build_number + 1)
gym(
scheme: "MyMobileApp",
export_method: "app-store",
output_directory: "builds"
)
upload_to_testflight(
skip_submission: true,
notify_tester: true
)
appcenter_distribute
end
desc "Release App Store"
lane :release do
screenshots
beta
upload_to_app_store
end
after_all do
clean_build_artifacts
end
endThis iOS Fastfile handles everything: scan for parallel tests, snapshot/frameit for auto marketing assets (13 iOS sizes), optimized gym AppStore export. latest_testflight_build_number avoids collisions. Pitfall: use skip_waiting_for_build_processing in CI for faster feedback.
Complete Android Fastfile with Play Store
default_platform(:android)
platform :android do
before_all do
# Gradle clean
gradle(task: "clean")
end
desc "Tests Android"
lane :test do
gradle(task: "test")
gradle(task: "connectedAndroidTest")
end
desc "Build et deploy beta (Internal Testing)"
lane :beta do
gradle(
task: "clean bundleRelease",
properties: {
"android.injected.signing.store.file": "keystore.jks",
"android.injected.signing.store.password": ENV["KEYSTORE_PASSWORD"]
}
)
upload_to_play_store(
track: "internal",
aab: "app/build/outputs/bundle/release/app-release.aab",
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
desc "Release Production"
lane :release do
increment_version_code
increment_version_name(append_version_code: false)
beta
upload_to_play_store(track: "production")
end
after_all do |lane|
sh("../gradlew clean")
end
endAndroid Fastfile uses gradle for custom tasks with ENV-injected signing (secure). upload_to_play_store with tracks (internal/production) for beta/GA. Advanced: auto increment_version_* bumps. Avoid hardcoded paths: use vars for multi-module scalability.
CI/CD Integration with GitHub Actions
Run fastlane beta on main pushes. Cache Bundler/Match for <5min builds. Use GitHub Secrets for MATCH_GIT_URL (private).
Multi-Platform GitHub Actions Workflow
name: CI/CD Fastlane
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test-ios:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
bundler-cache: true
- name: Install dependencies
run: bundle install
- name: Run tests
run: bundle exec fastlane test
env:
MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
beta-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: "r26b"
- name: Install Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Bundle & Beta
run: |
bundle install
bundle exec fastlane beta
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
GOOGLE_SERVICE_ACCOUNT_JSON: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }}
deploy-beta:
needs: [test-ios, beta-android]
runs-on: macos-14
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with: bundler-cache: true
- run: bundle exec fastlane beta
env:
FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 1200This workflow parallelizes iOS/Android tests and deploys beta on main. bundler-cache and setup-ndk optimize (<10min total). Conditional jobs (needs/if) ensure proper gating. Pitfall: Xcode timeouts → set FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT=1200.
Best Practices
- Modularity: One lane per responsibility (test/screenshots/deploy), reuse via
lane(:sub_lane). - Secrets management: Never hardcode; use
ENV+ GitHub/1Password CLI for auto-rotation. - Aggressive caching: Pods/DerivedData in CI,
readonly Matchfor 90% speedup. - Observability: Slack/Teams notifications via
notify, Slather for >80% coverage. - Rollback:
reset_git_repo+ tag backup before release.
Common Errors to Avoid
- Expired certs:
match nuke distribution+ regenerate; monitor with GitHub cron. - Screenshot failures: Ensure simulators launch (
snapshot_launch_timer: 30) and locales installed. - CI timeouts: Bump
FASTLANE_UPDATE_METHOD=globaland cache Bundler. - Version bump races: Lock
increment_build_numberwithrescueand retry.
Next Steps
Dive into Learni trainings on mobile CI/CD for Fastlane + GitLab/Terraform. Read Fastlane docs and contribute on GitHub. Advanced: integrate Firebase App Distribution and custom Ruby plugins.