Open source mobile build automation tool for iOS and Android.
- Summary
- Install
- Environment Variables
- Fastlane Template
- Examples
- Instantiate New Fastfile Config
- Lanes
- Build
- Actions
- Plugins
- DotEnv Support
- Code Signing
- Match
- Tests
- Screenshots
- Upload Artifacts
- Notifications
- Generating Lane Documentation
- Troubleshooting
- Other Resources
Useful for both local builds and CI/CD.
Integrates seamlessly with the App Store natively and Firebase App Distribution (via plugin).
Ruby-based declarative fastlane/Fastfile with many built-in constructs to do common tasks with a simple keyword.
Is reminiscent of many other build systems, eg. Fastlane lanes are equivalent of Makefile build targets.
Read the Mobile Builds pages for iOS and Android before moving to Fastlane as you will need to install things like Xcode for iOS.
https://docs.fastlane.tools/getting-started/ios/setup/
https://docs.fastlane.tools/getting-started/android/setup/
brew install fastlaneUpgrade fastlane using the standard brew command:
brew upgrade fastlaneCheck version:
fastlane -vgem install bundlerCreate a Gemfile:
source "https://rubygems.org"
gem "fastlane"Run bundler to install the gems from the Gemfile, in this case Fastlane:
bundle updateCommit the Gemfile and also the Gemfile.lock to pin the versions:
git add Gemfile Gemfile.lock
git commit -m "added Gemfile and Gemfile.lock" Gemfile Gemfile.lockExecute fastlane through bundler:
bundle exec fastlane [lane]Update fastlane:
bundle update fastlaneCheck new version:
fastlane -vor
bundle exec fastlane -vFastlane recommends settings these:
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8For CI/CD:
export FASTLANE_SKIP_UPDATE_CHECK=1https://github.com/HariSekhon/Templates/blob/master/fastlane/Fastfile
https://docs.fastlane.tools/actions/create_app_online/
If you want to do it the hard way from scratch instead of using the above template...
fastlane initThis will generate a skeleton fastlane/Fastfile, fastlane/Appfile, Gemfile, Gemfile.lock for you.
For publishing to App Store you need to populate the developer / company name:
PRODUCE_COMPANY_NAME="YOUR COMPANY NAME" fastlane initTo have Fastfile configuration written in Swift:
fastlane init swiftFastlane's 'lanes' are analogous to Makefile 'targets' - specifying one as an argument chooses which Fastlane code block to execute.
List the configured lanes in your project's fastlane/Fastfile:
fastlane lanesBuild a specific lane:
fastlane "$lane"A simple iOS example of defining a lane in your fastlane/Fastfile:
lane :build_and_deploy do
match(type: "appstore") # Fetch code-signing certificates
build_app(scheme: "MyApp") # Build the app
pilot # Upload the .ipa artifact to Apple TestFlight
endRun the lane via the command:
fastlane build_and_deploySince that lane's code block contains build_app() it'll run xcodebuild on iOS
and use xcbeautify if available, else xcpretty.
See the xcodebuild-formatters fastlane doc.
eg.
set -o pipefail && xcodebuild -workspace "$APP".xcworkspace -scheme "$SCHEME" -configuration "$CONFIGURATION" -destination 'generic/platform=iOS' -archivePath ./build/"$APP".xcarchive archive | tee /Users/"$USER"/Library/Logs/gym/"$APP-$SCHEME".log | xcprettyor
set -o pipefail && xcodebuild -workspace "$APP".xcworkspace -scheme "$SCHEME" -configuration "$CONFIGURATION" -destination 'generic/platform=iOS' -archivePath ./build/"$APP".xcarchive archive | tee /Users/"$USER"/Library/Logs/gym/"$APP-$SCHEME".log | xcbeautifyActions are steps to execute, usually built-in functions or functions provided by plugins to make it easier to build apps.
List all available actions on command line:
fastlane actionsGet usage details on any step action:
fastlane action "$action_name"Run an action the command line to test it without adding it to your Fastfile:
fastlane run notification message:"My Text" title:"The Title"This causes a Desktop pop-up notification.
https://docs.fastlane.tools/plugins/available-plugins/
https://docs.fastlane.tools/plugins/using-plugins/
https://docs.fastlane.tools/plugins/plugins-troubleshooting/
Find a plugin:
fastlane search_plugins "$query"Add plugin to project:
fastlane add_plugin "$plugin_name"This adds it to fastlane/Pluginfile which is a Gemfile that is referenced from the top level Gemfile because
fastlane adds this to the Gemfile:
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
eval_gemfile(plugins_path) if File.exist?(plugins_path)Install plugin dependencies:
fastlane install_pluginsUpdate all plugin versions:
fastlane update_pluginsRemove plugin by editing the Pluginfile:
"$EDITOR" fastlane/Pluginfileand removing line that looks like:
gem "fastlane-plugin-[plugin_name]"
Add this line to your Gemfile:
gem "dotenv"bundle updategit add Gemfile Gemfile.lock
git commit -m "added dotenv to Gemfile and Gemfile.lock" Gemfile Gemfile.lockAdd .env to .gitignore.
For Android builds using Gradle, add these properties to the gradle() action:
properties: {
"android.injected.signing.store.file" => ENV["JKS"],
"android.injected.signing.store.password" => ENV["JKS_KEYSTORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["JKS_KEY_ALIAS"],
"android.injected.signing.key.password" => ENV["JKS_KEY_PASSWORD"],
# Optional: Explicitly set signing algorithms (if needed)
"android.injected.signing.v2.signing.enabled" => "true",
"android.injected.signing.v1.signing.enabled" => "true" # not working according to apksigner verification
}Here I am importing the secrets from environment variables, eg.
using an uncommitted .env or pulling from a secrets manager using a .envrc (see Direnv).
For a full example see:
HariSekhon/Templates - fastlane/Fastfile
https://docs.fastlane.tools/actions/match/
Manages iOS code-signing credentials securely in a Git repo.
Sync your SSL signing certificates and Mobile Provisioning Profiles across devs or CI/CD builds using a separate Git repo, AWS S3 / GCP GCS bucket.
You need to have Apple Developer Portal credentials for Fastlane to verify the SSL certs when syncing them
Initialize a skeleton fastlane/Matchfile - run this and answer the prompts:
fastlane match initfastlane/Matchfile looks like this:
git_url("git@github.com:OWNER/REPO")
storage_mode("git")
type("development")Commit it:
git add fastlane/Matchfile &&
git commit -m "added Matchfile" fastlane/MatchfileIf you specified your repo like this you need to use an HTTPS token:
https://github.com/OWNER/REPO
For GitHub, generate one here:
https://github.com/settings/tokens
Then base64 encode it:
base64 <<< "$GITHUB_TOKEN"and export it:
export MATCH_GIT_BASIC_AUTHORIZATION="<base64_encoded_access_token>"Set up your CI/CD credentials to use an SSH Deploy Key specifically created for Fastlane to access the Matchfiles git repo.
ssh-keygen -f ~/.ssh/fastlane-ssh-keyCopy ~/.ssh/fastlane-ssh-key.pub to https://github.com/OWNER/REPO/settings/keys.
You can put the path to the SSH key in the environment variable for Fastlane to automatically pick it up:
export MATCH_GIT_PRIVATE_KEY="$HOME/.ssh/fastlane-ssh-key"Alternatively create a machine account access token and put it in the environment variable:
export MATCH_PASSWORD="ghp_a12b34cde5fabcdefabcd6efa78bcd9ef0ab" # Anonymized token example by anonymize.py from DevOps-Python-toolsGenerate a secure password:
pwgen -s 20 1export it for Match to pick it up:
export MATCH_PASSWORD="..."Set which branch you want to populate for that environment:
export MATCH_GIT_BRANCH='dev'Since I was only given the .p12 private key without the .cer public cert, I regenerated it like this:
openssl pkcs12 -in "$NAME.p12" -clcerts -nokeys -out /dev/stdout -passin env:CERT_PASSWORD |
sed -n '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p' > "$NAME.cer"If you get this error:
error:0308010C:digital envelope routines:inner_evp_generic_fetch:unsupported
Add the -legacy switch to the openssl command.
This command only takes one mobileprovision profile at a time so you may have to run it multiple times.
This will upload to Git, you may want to override your email address if this is for work:
export GIT_AUTHOR_EMAIL=hari.sekhon@domain.com
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"It'll prompt you for your .cer, then .p12, then .mobileprovision file:
fastlane match import \
--type development \
--profile "$HOME/Library/MobileDevice/Provisioning Profiles/$NAME.mobileprovision"Upload prod cert with appstore type to differentiate it later (this seems fairly arbitrary):
export MATCH_GIT_BRANCH='prod'fastlane match import \
--type appstore \
--profile "$HOME/Library/MobileDevice/Provisioning Profiles/$NAME.mobileprovision"Since the devs dumped a bunch of *.mobileprovision profiles on me without a clear naming convention,
I just imported all of them - this will prompt for the .cer, then .p12, then .mobileprovision profile on each
iteration and I can't find non-interactive switches for these so I just used a here doc (EOF) to automate it to be
non-interactive:
export MATCH_GIT_BRANCH='dev'NAME=My_Dev_Certfor mobileprovision_file in ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision; do
fastlane match import \
--type development \
--skip_certificate_matching <<-EOF || break
$NAME.cer
$NAME.p12
$mobileprovision_file
EOF
doneexport MATCH_GIT_BRANCH='prod'NAME=My_Prod_Certfor mobileprovision_file in ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision; do
fastlane match import \
--type appstore \
--skip_certificate_matching <<-EOF || break
$NAME.cer
$NAME.p12
$mobileprovision_file
EOF
doneDon't worry that the command outputs this, these are just the defaults:
+-------------------------------------------------------------------+
| Detected Values from './fastlane/Matchfile' |
+--------------+----------------------------------------------------+
| git_url | git@github.com:OWNER/REPO |
| storage_mode | git |
| type | development |
+--------------+----------------------------------------------------+
The --type is still respected and it creates under certs/distribution/... and profiles/appstore/... in the repo.
Add this to your fastlane/Fastfile dev lane:
ENV['MATCH_GIT_BRANCH'] = 'dev'
#match(type: 'development')
match # it'll default to development type from Matchfile - it's the branch that's important hereAdd this to your fastlane/Fastfile staging lane:
ENV['MATCH_GIT_BRANCH'] = 'staging'
matchAdd this to your fastlane/Fastfile prod lane:
ENV['MATCH_GIT_BRANCH'] = 'prod'
match(type: 'appstore')https://docs.fastlane.tools/actions/scan/
desc "Run all the tests"
lane :run_unit_tests do
scan(
scheme: "SomeApp",
clean: true,
devices: ["iPhone 13 Pro"],
slack_url: "https://hooks.slack.com/services/web_hook_id"
)
endfastlane run_unit_testsgradle(task: "test")https://docs.fastlane.tools/getting-started/ios/screenshots/
Automate taking app screenshots for the App Store or Play Store.
Fastlane also helps manage App Store or Play Store metadata.
https://docs.fastlane.tools/actions/snapshot/
https://docs.fastlane.tools/actions/screengrab/
Upload the built .ipa or .apk artifacts for iOS or Android respectively.
- iOS:
fastlane pilotfor TestFlight orfastlane deliverfor App Store Connect - Android:
fastlane supplyfor Google Play Store - Firebase App Distribution - can store either iOS or Android artifacts
https://docs.fastlane.tools/getting-started/ios/beta-deployment/
https://docs.fastlane.tools/actions/upload_to_testflight/
https://docs.fastlane.tools/getting-started/ios/appstore-deployment/
https://docs.fastlane.tools/actions/upload_to_app_store/
https://docs.fastlane.tools/getting-started/android/beta-deployment/
https://docs.fastlane.tools/getting-started/android/setup/#setting-up-supply
https://docs.fastlane.tools/actions/upload_to_play_store/
https://docs.fastlane.tools/getting-started/android/release-deployment/
Create a Google Service Account:
gcloud iam service-accounts create fastlane-upload \
--description="Service account for Fastlane APK upload" \
--display-name="Fastlane Upload"Create a json credential key:
gcloud iam service-accounts keys create \
~/.gcloud/"fastlane-upload-key-$CLOUDSDK_CORE_PROJECT.json" \
--iam-account="fastlane-upload@$CLOUDSDK_CORE_PROJECT.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding "$GOOGLE_PLAY_CLOUDSDK_CORE_PROJECT" \
--member="serviceAccount:fastlane-upload@$CLOUDSDK_CORE_PROJECT.iam.gserviceaccount.com" \
--role="roles/viewer"Go to Google Play Store and assign Release Manager to the fastlane-upload service account.
Validate it has permissions:
fastlane run validate_play_store_json_key json_key:"$HOME/.gcloud/fastlane-upload-key-$CLOUDSDK_CORE_PROJECT.json"Configure fastlane/AppFile:
json_key_file("/path/to/your/fastlane-upload-key.json")
package_name("com.harisekhon.app")
Configure fastlane/Fastfile to add this to your lane:
upload_to_play_store(track: 'beta')See parameters for the upload_to_play_store() action:
fastlane action upload_to_play_storeUsing plugin:
firebase/fastlane-plugin-firebase_app_distribution
https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane
fastlane add_plugin firebase_app_distributiongit add Gemfile Gemfile.lock fastlane/Pluginfile &&
git commit -m "added Fastlane Firebase plugin" Gemfile Gemfile.lock fastlane/PluginfileThen in your fastlane/Fastfile call it inside a lane:
release = firebase_app_distribution(
# for iOS
app: "1:123456789012:ios:1a2b3cd45e67890fa12b34",
# for Android
#app: "1:123456789012:android:1a2b3cd45e67890fa12b34",
testers: "hari@domain.com",
release_notes: "Fastlane Dev Release",
upload_timeout: 900
)Fastlane can send notifications to Slack, email etc for completed builds.
Running fastlane automatically (re)generates fastlane/README.md documenting your lanes and their descriptions
taken from the desc statement just before the lane definition in fastlane/Fastfile.
If you want to just update the fastlane/README.md without executing the fastlane actions:
fastlane docsfastlane "$lane" --verbose
https://www.kodeco.com/233168-fastlane-tutorial-getting-started
https://www.kodeco.com/26869030-fastlane-tutorial-for-android-getting-started