Agent skill

fastlane

iOS and Android app deployment automation with Fastlane. Use when building, signing, and distributing apps to TestFlight, App Store, or Google Play. Covers Match code signing, CI/CD keychain setup, and Tauri integration. Triggers on Fastfile, Appfile, Matchfile, fastlane commands.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/fastlane

SKILL.md

Fastlane Deployment Skill

Automate iOS and Android app building, code signing, and distribution.

When to Apply

Reference this skill when:

  • Setting up Fastlane for a new project
  • Configuring code signing with Match
  • Building lanes for TestFlight or App Store distribution
  • Setting up Android Play Store deployment
  • Debugging code signing or build failures
  • Configuring CI/CD pipelines for mobile apps
  • Integrating Fastlane with Tauri v2 projects

Quick Start

Minimal Fastfile Structure

ruby
default_platform(:ios)

platform :ios do
  desc "Build and upload to TestFlight"
  lane :beta do
    match(type: "appstore", readonly: true)
    build_app(scheme: "MyApp")
    upload_to_testflight
  end
end

platform :android do
  desc "Build and upload to Play Store beta"
  lane :beta do
    gradle(task: "bundle", build_type: "Release")
    upload_to_play_store(track: "beta")
  end
end

Tool Aliases

Fastlane provides shorter aliases for common actions:

Alias Action Purpose
gym build_app Build and sign iOS/macOS apps
pilot upload_to_testflight Upload to TestFlight
deliver upload_to_app_store Submit to App Store
supply upload_to_play_store Upload to Google Play
match sync_code_signing Sync certificates and profiles
cert get_certificates Download signing certificates
sigh get_provisioning_profile Download provisioning profiles
scan run_tests Run unit and UI tests
snapshot capture_screenshots Automated App Store screenshots
frameit frame_screenshots Add device frames to screenshots
produce create_app_online Create app in App Store Connect
pem get_push_certificate Download push notification certs
precheck check_app_store_metadata Validate metadata before submission

iOS Workflows

TestFlight Deployment

ruby
lane :beta do
  # Sync code signing
  match(type: "appstore", readonly: true)

  # Increment build number
  increment_build_number(
    build_number: Time.now.utc.strftime("%y%m%d%H%M")
  )

  # Build the app
  build_app(
    scheme: "MyApp",
    export_method: "app-store",
    output_directory: "./build"
  )

  # Upload to TestFlight
  upload_to_testflight(
    skip_waiting_for_build_processing: true,
    uses_non_exempt_encryption: false
  )
end

App Store Release

ruby
lane :release do
  match(type: "appstore", readonly: true)

  build_app(
    scheme: "MyApp",
    export_method: "app-store"
  )

  upload_to_app_store(
    skip_screenshots: true,
    skip_metadata: true,
    submit_for_review: false
  )
end

App Store Connect API Key

ruby
def api_key
  app_store_connect_api_key(
    key_id: ENV['APP_STORE_CONNECT_API_KEY_KEY_ID'],
    issuer_id: ENV['APP_STORE_CONNECT_API_KEY_ISSUER_ID'],
    key_content: ENV['APP_STORE_CONNECT_API_KEY_KEY'],
    is_key_content_base64: true
  )
end

lane :beta do
  upload_to_testflight(api_key: api_key)
end

Android Workflows

Play Store Beta

ruby
platform :android do
  lane :beta do
    gradle(
      task: "bundle",
      build_type: "Release",
      project_dir: "./android"
    )

    upload_to_play_store(
      track: "beta",
      aab: "./android/app/build/outputs/bundle/release/app-release.aab",
      skip_upload_metadata: true,
      skip_upload_images: true
    )
  end
end

Play Store Production

ruby
lane :release do
  gradle(task: "bundle", build_type: "Release")

  upload_to_play_store(
    track: "production",
    aab: "./android/app/build/outputs/bundle/release/app-release.aab"
  )
end

Code Signing with Match

Initial Setup

bash
# Initialize Match configuration
fastlane match init

# Generate certificates (run once per team)
fastlane match appstore
fastlane match development

Matchfile Configuration

ruby
# fastlane/Matchfile
git_url("git@github.com:your-org/certificates.git")
storage_mode("git")
type("appstore")
app_identifier("com.example.app")
team_id("TEAM_ID")

S3/MinIO Storage (Alternative to Git)

ruby
# Matchfile for S3-compatible storage
storage_mode("s3")
s3_region("us-east-1")
s3_bucket("certificates")
s3_access_key(ENV['AWS_ACCESS_KEY_ID'])
s3_secret_access_key(ENV['AWS_SECRET_ACCESS_KEY'])

# For MinIO, set endpoint
# ENV['AWS_ENDPOINT_URL'] = "https://minio.example.com"

Using Match in Lanes

ruby
lane :beta do
  match(
    type: "appstore",
    readonly: true,  # Don't create new certs
    keychain_name: ENV['CI'] ? "fastlane_ci" : nil,
    keychain_password: ENV['CI'] ? "fastlane_ci_password" : nil
  )
end

CI/CD Integration

Keychain Setup for CI

ruby
CI_KEYCHAIN_NAME = "fastlane_ci"
CI_KEYCHAIN_PASSWORD = "fastlane_ci_password"

def setup_ci_keychain
  if ENV['CI']
    create_keychain(
      name: CI_KEYCHAIN_NAME,
      password: CI_KEYCHAIN_PASSWORD,
      default_keychain: true,
      unlock: true,
      timeout: 3600,
      lock_when_sleeps: false,
      add_to_search_list: true
    )
  end
end

def cleanup_ci_keychain
  if ENV['CI']
    delete_keychain(name: CI_KEYCHAIN_NAME)
  end
end

lane :beta do
  setup_ci_keychain
  match(
    type: "appstore",
    keychain_name: CI_KEYCHAIN_NAME,
    keychain_password: CI_KEYCHAIN_PASSWORD
  )
  # ... build and upload
  cleanup_ci_keychain
end

GitHub Actions Example

yaml
# .github/workflows/ios.yml
name: iOS Build
on: [push]

jobs:
  build:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Fastlane
        run: brew install fastlane

      - name: Build and Deploy
        env:
          MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
          MATCH_GIT_URL: ${{ secrets.MATCH_GIT_URL }}
          APP_STORE_CONNECT_API_KEY_KEY_ID: ${{ secrets.ASC_KEY_ID }}
          APP_STORE_CONNECT_API_KEY_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
          APP_STORE_CONNECT_API_KEY_KEY: ${{ secrets.ASC_KEY_CONTENT }}
          CI: true
        run: fastlane ios beta

Environment Variables

iOS (App Store Connect)

Variable Description
APP_STORE_CONNECT_API_KEY_KEY_ID API Key ID from App Store Connect
APP_STORE_CONNECT_API_KEY_ISSUER_ID Issuer ID from App Store Connect
APP_STORE_CONNECT_API_KEY_KEY Base64-encoded .p8 key content
MATCH_PASSWORD Encryption password for Match
MATCH_GIT_URL Git repository URL for certificates

Android (Google Play)

Variable Description
SUPPLY_JSON_KEY_DATA Google Play service account JSON (base64)
SUPPLY_JSON_KEY Path to service account JSON file

Encoding API Keys

bash
# Encode .p8 file to base64
base64 -i AuthKey_XXXXXXXXXX.p8 | tr -d '\n'

# Encode Google Play JSON
base64 -i play-store-key.json | tr -d '\n'

App Store Requirements

Metadata Character Limits

Field Limit
App Name 30 characters
Subtitle 30 characters
Keywords 100 characters
Description 4000 characters
Release Notes 4000 characters
Promotional Text 170 characters

Screenshot Requirements (2024)

Device Size Required
iPhone 6.7" 1290 x 2796 Yes (primary)
iPhone 6.5" 1284 x 2778 Alternative
iPhone 5.5" 1242 x 2208 Optional
iPad Pro 12.9" 2048 x 2732 If iPad supported
iPad Pro 11" 1668 x 2388 Alternative

Known Issues Prevention

Issue Root Cause Solution
"Multiple commands produce" Duplicate files in build Remove duplicates from sources
Code signing fails in CI No keychain access Use create_keychain + Match
Build number rejected Duplicate build number Use timestamp: Time.now.utc.strftime("%y%m%d%H%M")
Profile not found Wrong Match type Use appstore for TestFlight/App Store
Invalid PEM format Wrong key encoding Ensure base64 with is_key_content_base64: true
"Missing compliance" Encryption declaration Set uses_non_exempt_encryption: false
Gradle build fails Missing SDK/NDK Set ANDROID_HOME and ANDROID_NDK_HOME

Tauri Integration

Version from Cargo.toml

ruby
ROOT_DIR = File.expand_path("..", __dir__)

def get_app_version
  cargo_toml = File.read("#{ROOT_DIR}/src-tauri/Cargo.toml")
  if cargo_toml =~ /^version\s*=\s*"([^"]+)"/
    $1
  else
    "1.0.0"
  end
end

def get_next_build_number
  Time.now.utc.strftime("%y%m%d%H%M").to_i
end

Update tauri.conf.json

ruby
def update_tauri_config_version
  app_version = get_app_version
  build_number = get_next_build_number

  tauri_conf_path = "#{ROOT_DIR}/src-tauri/tauri.conf.json"
  tauri_conf = JSON.parse(File.read(tauri_conf_path))
  tauri_conf["version"] = app_version
  tauri_conf["bundle"] ||= {}
  tauri_conf["bundle"]["iOS"] ||= {}
  tauri_conf["bundle"]["iOS"]["bundleVersion"] = build_number.to_s

  File.write(tauri_conf_path, JSON.pretty_generate(tauri_conf))
end

Fix Tauri project.yml (Duplicate libapp.a)

ruby
def fix_tauri_project_yml
  project_yml_path = "#{ROOT_DIR}/src-tauri/gen/apple/project.yml"
  return unless File.exist?(project_yml_path)

  content = File.read(project_yml_path)

  # Remove "- path: Externals" to prevent duplicate libapp.a
  if content.include?("- path: Externals")
    content.gsub!(/^\s*- path: Externals\n/, "")
    File.write(project_yml_path, content)
    sh("cd #{ROOT_DIR}/src-tauri/gen/apple && xcodegen generate")
  end
end

Tauri iOS Build Lane

ruby
lane :beta do
  match(type: "appstore", readonly: true)

  update_tauri_config_version
  fix_tauri_project_yml

  # Configure signing
  update_code_signing_settings(
    use_automatic_signing: false,
    path: "#{ROOT_DIR}/src-tauri/gen/apple/MyApp.xcodeproj",
    team_id: TEAM_ID,
    bundle_identifier: APP_IDENTIFIER,
    profile_name: "match AppStore #{APP_IDENTIFIER}",
    code_sign_identity: "Apple Distribution"
  )

  # Build with Tauri
  sh("cd #{ROOT_DIR}/src-tauri && npx tauri ios build --export-method app-store-connect")

  upload_to_testflight(
    ipa: "#{ROOT_DIR}/src-tauri/gen/apple/build/arm64/MyApp.ipa",
    skip_waiting_for_build_processing: true
  )
end

Tauri Android Build Lane

ruby
platform :android do
  lane :beta do
    ENV['ANDROID_HOME'] = "/opt/homebrew/share/android-commandlinetools"
    ENV['ANDROID_NDK_HOME'] = "#{ENV['ANDROID_HOME']}/ndk/28.2.13676358"

    # Update version in tauri.conf.json
    app_version = get_app_version
    build_number = get_next_build_number

    tauri_conf_path = "#{ROOT_DIR}/src-tauri/tauri.conf.json"
    tauri_conf = JSON.parse(File.read(tauri_conf_path))
    tauri_conf["version"] = app_version
    tauri_conf["bundle"]["android"] ||= {}
    tauri_conf["bundle"]["android"]["versionCode"] = build_number
    File.write(tauri_conf_path, JSON.pretty_generate(tauri_conf))

    # Build with Tauri
    sh("cd #{ROOT_DIR}/src-tauri && npx tauri android build")

    upload_to_play_store(
      track: "beta",
      aab: "#{ROOT_DIR}/src-tauri/gen/android/app/build/outputs/bundle/release/app-release.aab"
    )
  end
end

Essential Commands

bash
# Initialize Fastlane
fastlane init

# List available actions
fastlane actions

# Run a specific lane
fastlane ios beta
fastlane android release

# Debug with verbose output
fastlane ios beta --verbose

# Run Match commands
fastlane match appstore
fastlane match development
fastlane match nuke distribution  # Reset all distribution certs

Sources

Didn't find tool you were looking for?

Be as detailed as possible for better results