Fastlane Cheat Sheet
Overview
Fastlane is an open-source platform for automating the building, testing, and releasing of iOS and Android applications. It handles tedious tasks like generating screenshots, managing code signing certificates and provisioning profiles, running tests, and deploying apps to the App Store and Google Play Store. Fastlane uses a Ruby-based DSL where automation steps are defined as “lanes” in a Fastfile, with each step calling built-in “actions” that wrap complex platform tools.
Fastlane dramatically reduces the time and complexity of mobile release processes. It manages iOS code signing through the “match” system (storing certificates in a shared git repo or cloud storage), automates screenshot generation across multiple device sizes and languages, handles beta distribution through TestFlight and Firebase App Distribution, and manages metadata and app store listings. The tool integrates seamlessly with CI/CD systems like GitHub Actions, Bitrise, CircleCI, and Jenkins.
Installation
# Install via Homebrew (recommended for macOS)
brew install fastlane
# Install via RubyGems
sudo gem install fastlane
# Install via Bundler (recommended for teams)
# Create Gemfile
cat > Gemfile << 'EOF'
source "https://rubygems.org"
gem "fastlane"
EOF
bundle install
# Initialize in project
cd my-app
fastlane init
# iOS: Choose setup option
# 1. Automate screenshots
# 2. Automate beta distribution
# 3. Automate App Store distribution
# 4. Manual setup
# Android
fastlane init
# Provide path to Google Play JSON key
# Verify
fastlane --version
fastlane env # Print environment info
Project Structure
my-app/
├── Gemfile # Ruby dependencies
├── Gemfile.lock
└── fastlane/
├── Fastfile # Lane definitions
├── Appfile # App identifiers
├── Matchfile # Code signing config
├── Deliverfile # App Store metadata config
├── Scanfile # Test configuration
├── Screengrabfile # Android screenshot config
├── Snapfile # iOS screenshot config
├── Pluginfile # Fastlane plugins
├── metadata/ # App Store metadata
│ ├── en-US/
│ │ ├── description.txt
│ │ ├── keywords.txt
│ │ ├── release_notes.txt
│ │ └── name.txt
│ └── review_information/
├── screenshots/ # Generated screenshots
└── report.xml # Build report
Appfile
# fastlane/Appfile
# iOS
app_identifier("com.example.myapp")
apple_id("developer@example.com")
team_id("ABCDEF1234")
itc_team_id("12345678")
# Android
json_key_file("path/to/google-play-key.json")
package_name("com.example.myapp")
# Per-lane overrides
for_platform :ios do
for_lane :enterprise do
app_identifier("com.example.myapp.enterprise")
end
end
Fastfile (Lanes)
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Run unit tests"
lane :test do
scan(
scheme: "MyApp",
devices: ["iPhone 15 Pro"],
clean: true,
code_coverage: true
)
end
desc "Build and push to TestFlight"
lane :beta do
ensure_git_clean
increment_build_number(xcodeproj: "MyApp.xcodeproj")
match(type: "appstore")
build_app(
scheme: "MyApp",
workspace: "MyApp.xcworkspace",
export_method: "app-store",
output_directory: "./build",
output_name: "MyApp.ipa"
)
upload_to_testflight(
skip_waiting_for_build_processing: true
)
slack(
message: "New iOS beta uploaded to TestFlight!",
slack_url: ENV["SLACK_WEBHOOK"]
)
commit_version_bump(message: "Bump build number [skip ci]")
push_to_git_remote
end
desc "Deploy to App Store"
lane :release do
ensure_git_clean
increment_version_number(bump_type: "patch")
increment_build_number
match(type: "appstore")
build_app(scheme: "MyApp", workspace: "MyApp.xcworkspace")
upload_to_app_store(
force: true,
submit_for_review: true,
automatic_release: true,
submission_information: {
add_id_info_uses_idfa: false
}
)
commit_version_bump(message: "Release v#{lane_context[SharedValues::VERSION_NUMBER]}")
add_git_tag
push_to_git_remote
end
desc "Generate screenshots"
lane :screenshots do
capture_ios_screenshots
upload_to_app_store(
skip_binary_upload: true,
skip_metadata: true,
overwrite_screenshots: true
)
end
error do |lane, exception|
slack(
message: "#{lane} failed: #{exception.message}",
slack_url: ENV["SLACK_WEBHOOK"],
success: false
)
end
end
platform :android do
desc "Run tests"
lane :test do
gradle(task: "test")
end
desc "Build and deploy to Play Store beta"
lane :beta do
gradle(
task: "bundle",
build_type: "Release",
properties: {
"android.injected.signing.store.file" => ENV["KEYSTORE_PATH"],
"android.injected.signing.store.password" => ENV["KEYSTORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["KEY_ALIAS"],
"android.injected.signing.key.password" => ENV["KEY_PASSWORD"]
}
)
upload_to_play_store(
track: "beta",
aab: "android/app/build/outputs/bundle/release/app-release.aab"
)
end
desc "Deploy to Play Store production"
lane :release do
gradle(task: "bundle", build_type: "Release")
upload_to_play_store(
track: "production",
rollout: "0.1" # 10% staged rollout
)
end
end
Code Signing (Match)
# fastlane/Matchfile
git_url("https://github.com/myorg/certificates.git")
storage_mode("git")
type("appstore")
app_identifier(["com.example.myapp"])
team_id("ABCDEF1234")
# Initialize match (creates certs and profiles)
fastlane match development
fastlane match appstore
fastlane match adhoc
# Read-only mode (CI/CD)
fastlane match appstore --readonly
# Nuke and recreate (when certs expire)
fastlane match nuke development
fastlane match nuke appstore
# Using cloud storage instead of git
# In Matchfile:
# storage_mode("google_cloud")
# google_cloud_bucket_name("my-certs-bucket")
Common Actions
| Action | Description |
|---|---|
build_app / gym | Build iOS app (IPA) |
scan | Run tests |
match | Manage code signing |
deliver | Upload to App Store |
pilot / upload_to_testflight | Upload to TestFlight |
snapshot / capture_ios_screenshots | Generate iOS screenshots |
screengrab | Generate Android screenshots |
gradle | Run Android Gradle tasks |
supply / upload_to_play_store | Upload to Google Play |
increment_build_number | Bump build number |
increment_version_number | Bump version |
slack | Send Slack notification |
firebase_app_distribution | Distribute via Firebase |
cocoapods | Run pod install |
CLI Commands
# Run a lane
fastlane ios beta
fastlane android release
# List available lanes
fastlane list
# Run single action
fastlane run increment_build_number
# Get action documentation
fastlane action build_app
fastlane actions # List all actions
# Install plugins
fastlane add_plugin firebase_app_distribution
fastlane add_plugin badge
# Update fastlane
fastlane update_fastlane
bundle update fastlane
Advanced Usage
# Before/after hooks
before_all do
ensure_git_clean
cocoapods(repo_update: true)
end
after_all do |lane|
clean_build_artifacts
notification(
title: "Fastlane",
message: "#{lane} completed successfully!"
)
end
# Environment variables
lane :deploy do
api_key = app_store_connect_api_key(
key_id: ENV["ASC_KEY_ID"],
issuer_id: ENV["ASC_ISSUER_ID"],
key_filepath: ENV["ASC_KEY_PATH"],
)
pilot(api_key: api_key)
end
# Multiple app targets
lane :build_all do
["MyApp", "MyAppPro", "MyAppLite"].each do |scheme|
build_app(
scheme: scheme,
output_name: "#{scheme}.ipa"
)
end
end
# Firebase App Distribution
lane :firebase_beta do
build_app(scheme: "MyApp")
firebase_app_distribution(
app: "1:123456789:ios:abcdef",
groups: "internal-testers",
release_notes: changelog_from_git_commits
)
end
# Conditional logic
lane :smart_deploy do
if git_branch == "main"
release
elsif git_branch.start_with?("release/")
beta
else
test
end
end
Troubleshooting
| Issue | Solution |
|---|---|
| ”No signing certificate found” | Run fastlane match to sync certificates |
| Code signing issues | Use match system; check team ID and bundle identifier |
| ”Build failed” | Check Xcode build settings; run xcodebuild manually to debug |
| Play Store upload rejected | Verify JSON key permissions; check AAB signing |
| Match git clone fails | Check SSH keys; use HTTPS URL with MATCH_PASSWORD env var |
| Screenshots wrong size | Update device list in Snapfile; check simulator availability |
| Slow builds in CI | Use derived_data_path to cache; enable skip_build where possible |
| ”Could not find gem” | Run bundle install; check Ruby version compatibility |
| TestFlight processing timeout | Use skip_waiting_for_build_processing: true |
| Lane not found | Check platform declaration; verify lane name spelling |