Fastlane Setup Guide for iOS TestFlight Deployment

image

Rostislav Ulinets

image

Introduction

This guide will walk you through setting up Fastlane for automating iOS app deployment to TestFlight. The setup uses App Store Connect API keys, matches for certificate management, and handles build number incrementation automatically. This configuration works both locally on developer machines and seamlessly integrates with GitHub Actions or other CI systems.

Prerequisite

  • macOS with Xcode installed
  • An Apple Developer account
  • A GitHub account (for certificate storage)
  • Ruby installed (comes pre-installed on macOS)

Step 1: Install Fastlane

Navigate to your iOS project directory in the terminal and run the following commands:

# Navigate to your iOS project directory

cd ios

# Install Fastlane using Ruby's gem package manager

gem install fastlane

# Initialize Fastlane in your project

fastlane init

# Navigate to your iOS project directory

cd ios

# Install Fastlane using Ruby's gem package manager

gem install fastlane

# Initialize Fastlane in your project

fastlane init

# Navigate to your iOS project directory

cd ios

# Install Fastlane using Ruby's gem package manager

gem install fastlane

# Initialize Fastlane in your project

fastlane init

# Navigate to your iOS project directory

cd ios

# Install Fastlane using Ruby's gem package manager

gem install fastlane

# Initialize Fastlane in your project

fastlane init

Step 2: Choose Fastlane Setup Options

Fastlane will ask what you want to use it for. For TestFlight deployment, select the option for “Automate beta distribution to TestFlight.”

Step 3: Authenticate with Apple ID

During the initialization process, Fastlane will prompt you to enter your Apple ID email address. This step is crucial – you must enter the complete email address associated with your Apple Developer account. Fastlane requires this information to access App Store Connect and Apple Developer Portal on your behalf.
For example, when prompted, enter: your.name@example.com

Step 4: Initialize Match for Certificate Management

Match is a Fastlane tool that helps manage your iOS certificates and provisioning profiles securely.

# Initialize Match

fastlane match init

# Initialize Match

fastlane match init

# Initialize Match

fastlane match init

# Initialize Match

fastlane match init

# Initialize Match

fastlane match init

Step 5: Configure Environment Variables

1. Сreate a .env file in your Fastlane folder to store sensitive information securely:

2. Add the file to your .gitignore inside the iOS folder to prevent it from being committed to your repository:

# Navigate to your iOS folder first
cd ios

# Verify you're in the correct location
pwd
# Should show /path/to/your-project/ios

# Create or edit the .gitignore file
touch .gitignore

# Open the file with your preferred editor and add:
# fastlane/.env

# Navigate to your iOS folder first
cd ios

# Verify you're in the correct location
pwd
# Should show /path/to/your-project/ios

# Create or edit the .gitignore file
touch .gitignore

# Open the file with your preferred editor and add:
# fastlane/.env

# Navigate to your iOS folder first
cd ios

# Verify you're in the correct location
pwd
# Should show /path/to/your-project/ios

# Create or edit the .gitignore file
touch .gitignore

# Open the file with your preferred editor and add:
# fastlane/.env

# Navigate to your iOS folder first
cd ios

# Verify you're in the correct location
pwd
# Should show /path/to/your-project/ios

# Create or edit the .gitignore file
touch .gitignore

# Open the file with your preferred editor and add:
# fastlane/.env

# Navigate to your iOS folder first
cd ios

# Verify you're in the correct location
pwd
# Should show /path/to/your-project/ios

# Create or edit the .gitignore file
touch .gitignore

# Open the file with your preferred editor and add:
# fastlane/.env

Step 6: Set Up App Store Connect API Access

1. To automate the deployment process, you’ll need to set up API access to App Store Connect:

  • Access Apple App Store Connect by navigating to https://appstoreconnect.apple.com/ in your web browser. You’ll need to sign in with your Apple Developer account credentials to proceed.

2. After logging in to App Store Connect, click on “Users and Access” in the main navigation menu. This section allows you to manage team members and their permissions for your app.

3. In the Users and Access section, you’ll see a horizontal navigation menu with options including People, Sandbox, Integrations, and Xcode Cloud. Click on “Integrations.” Then, in the left sidebar, under “Keys,” select “App Store Connect API.”

4. Create a new API key by clicking the plus (+) button located on the right side of the word “Active” at the center of the page. In the dialog that appears, select “App Manager” permissions to ensure your API key has the necessary access rights for app management.

5. After creating the API key, collect and store the following credentials in your project’s .env file:

  • ASC_KEY_ID: Copy the Key ID from the “KEY ID” column in the keys list 
  • ASC_ISSUER_ID: Find this at the top of the page labeled “Issuer ID”
  • ASC_KEY_PATH: Download the private key file (.p8) and note its full path on your system

Add these to your .env file in this format:

ASC_KEY_ID="YOUR_KEY_ID"
ASC_ISSUER_ID="YOUR_ISSUER_ID"
ASC_KEY_PATH="/full/path/to/your/AuthKey_KEYID.p8"

ASC_KEY_ID="YOUR_KEY_ID"
ASC_ISSUER_ID="YOUR_ISSUER_ID"
ASC_KEY_PATH="/full/path/to/your/AuthKey_KEYID.p8"

ASC_KEY_ID="YOUR_KEY_ID"
ASC_ISSUER_ID="YOUR_ISSUER_ID"
ASC_KEY_PATH="/full/path/to/your/AuthKey_KEYID.p8"

ASC_KEY_ID="YOUR_KEY_ID"
ASC_ISSUER_ID="YOUR_ISSUER_ID"
ASC_KEY_PATH="/full/path/to/your/AuthKey_KEYID.p8"

ASC_KEY_ID="YOUR_KEY_ID"
ASC_ISSUER_ID="YOUR_ISSUER_ID"
ASC_KEY_PATH="/full/path/to/your/AuthKey_KEYID.p8"

Important: Make sure to use the absolute path to your key file for ASC_KEY_PATH, and keep your .p8 file secure as it contains sensitive authentication information.

Step 7: Create Certificates and Provisioning Profiles

Add the following lane to your Fastfile:

desc "Create new certificates and provisioning profiles"

lane :create_certificates_and_profiles do

  # Create for appstore

  match(

    type: "appstore",

    force: true,

    readonly: false,

  )

  # Create for development

  match(

    type: "development",

    force: true,

    readonly: false,

  )

  # Create for adhoc

  match(

    type: "adhoc",

    force: true,

    readonly: false,

  )

end

desc "Create new certificates and provisioning profiles"

lane :create_certificates_and_profiles do

  # Create for appstore

  match(

    type: "appstore",

    force: true,

    readonly: false,

  )

  # Create for development

  match(

    type: "development",

    force: true,

    readonly: false,

  )

  # Create for adhoc

  match(

    type: "adhoc",

    force: true,

    readonly: false,

  )

end

desc "Create new certificates and provisioning profiles"

lane :create_certificates_and_profiles do

  # Create for appstore

  match(

    type: "appstore",

    force: true,

    readonly: false,

  )

  # Create for development

  match(

    type: "development",

    force: true,

    readonly: false,

  )

  # Create for adhoc

  match(

    type: "adhoc",

    force: true,

    readonly: false,

  )

end

desc "Create new certificates and provisioning profiles"

lane :create_certificates_and_profiles do

  # Create for appstore

  match(

    type: "appstore",

    force: true,

    readonly: false,

  )

  # Create for development

  match(

    type: "development",

    force: true,

    readonly: false,

  )

  # Create for adhoc

  match(

    type: "adhoc",

    force: true,

    readonly: false,

  )

end

desc "Create new certificates and provisioning profiles"

lane :create_certificates_and_profiles do

  # Create for appstore

  match(

    type: "appstore",

    force: true,

    readonly: false,

  )

  # Create for development

  match(

    type: "development",

    force: true,

    readonly: false,

  )

  # Create for adhoc

  match(

    type: "adhoc",

    force: true,

    readonly: false,

  )

end

Run this lane to create all necessary certificates and profiles:

fastlane create_certificates_and_profiles

fastlane create_certificates_and_profiles

fastlane create_certificates_and_profiles

fastlane create_certificates_and_profiles

fastlane create_certificates_and_profiles

Step 8: Create a TestFlight Deployment Lane

Add the following lane to your Fastfile to handle the deployment process:

desc "Build and upload to TestFlight"
  lane :upload_to_testflight_function do
    setup_ci
    
    # Get app identifier and team ID from Appfile
    app_id = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
    team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)

    match_profile = "match AppStore #{app_id}"
    
    # Configure App Store Connect API
    app_store_connect_api_key(
      key_id: ENV["ASC_KEY_ID"],
      issuer_id: ENV["ASC_ISSUER_ID"],
      key_filepath: ENV["ASC_KEY_PATH"],
    )
    
    # Fetch certificates and profiles
    sync_code_signing(
      type: "appstore",
      readonly: true,
      app_identifier: app_id
    )
    
    # Update project settings to use match provisioning profile
    update_code_signing_settings(
      use_automatic_signing: false,
      path: "Runner.xcodeproj",
      team_id: team_id,
      profile_name: match_profile,
      code_sign_identity: "iPhone Distribution"
    )
    
    # Increment build number
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )
    
    # Build the app
    build_app(
      scheme: "development", # Replace with your app's scheme name
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          app_id => match_profile
        }
      }
    )
    
    # Upload to TestFlight
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end

desc "Build and upload to TestFlight"
  lane :upload_to_testflight_function do
    setup_ci
    
    # Get app identifier and team ID from Appfile
    app_id = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
    team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)

    match_profile = "match AppStore #{app_id}"
    
    # Configure App Store Connect API
    app_store_connect_api_key(
      key_id: ENV["ASC_KEY_ID"],
      issuer_id: ENV["ASC_ISSUER_ID"],
      key_filepath: ENV["ASC_KEY_PATH"],
    )
    
    # Fetch certificates and profiles
    sync_code_signing(
      type: "appstore",
      readonly: true,
      app_identifier: app_id
    )
    
    # Update project settings to use match provisioning profile
    update_code_signing_settings(
      use_automatic_signing: false,
      path: "Runner.xcodeproj",
      team_id: team_id,
      profile_name: match_profile,
      code_sign_identity: "iPhone Distribution"
    )
    
    # Increment build number
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )
    
    # Build the app
    build_app(
      scheme: "development", # Replace with your app's scheme name
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          app_id => match_profile
        }
      }
    )
    
    # Upload to TestFlight
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end

desc "Build and upload to TestFlight"
  lane :upload_to_testflight_function do
    setup_ci
    
    # Get app identifier and team ID from Appfile
    app_id = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
    team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)

    match_profile = "match AppStore #{app_id}"
    
    # Configure App Store Connect API
    app_store_connect_api_key(
      key_id: ENV["ASC_KEY_ID"],
      issuer_id: ENV["ASC_ISSUER_ID"],
      key_filepath: ENV["ASC_KEY_PATH"],
    )
    
    # Fetch certificates and profiles
    sync_code_signing(
      type: "appstore",
      readonly: true,
      app_identifier: app_id
    )
    
    # Update project settings to use match provisioning profile
    update_code_signing_settings(
      use_automatic_signing: false,
      path: "Runner.xcodeproj",
      team_id: team_id,
      profile_name: match_profile,
      code_sign_identity: "iPhone Distribution"
    )
    
    # Increment build number
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )
    
    # Build the app
    build_app(
      scheme: "development", # Replace with your app's scheme name
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          app_id => match_profile
        }
      }
    )
    
    # Upload to TestFlight
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end

desc "Build and upload to TestFlight"
  lane :upload_to_testflight_function do
    setup_ci
    
    # Get app identifier and team ID from Appfile
    app_id = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
    team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)

    match_profile = "match AppStore #{app_id}"
    
    # Configure App Store Connect API
    app_store_connect_api_key(
      key_id: ENV["ASC_KEY_ID"],
      issuer_id: ENV["ASC_ISSUER_ID"],
      key_filepath: ENV["ASC_KEY_PATH"],
    )
    
    # Fetch certificates and profiles
    sync_code_signing(
      type: "appstore",
      readonly: true,
      app_identifier: app_id
    )
    
    # Update project settings to use match provisioning profile
    update_code_signing_settings(
      use_automatic_signing: false,
      path: "Runner.xcodeproj",
      team_id: team_id,
      profile_name: match_profile,
      code_sign_identity: "iPhone Distribution"
    )
    
    # Increment build number
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )
    
    # Build the app
    build_app(
      scheme: "development", # Replace with your app's scheme name
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          app_id => match_profile
        }
      }
    )
    
    # Upload to TestFlight
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end

desc "Build and upload to TestFlight"
  lane :upload_to_testflight_function do
    setup_ci
    
    # Get app identifier and team ID from Appfile
    app_id = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
    team_id = CredentialsManager::AppfileConfig.try_fetch_value(:team_id)

    match_profile = "match AppStore #{app_id}"
    
    # Configure App Store Connect API
    app_store_connect_api_key(
      key_id: ENV["ASC_KEY_ID"],
      issuer_id: ENV["ASC_ISSUER_ID"],
      key_filepath: ENV["ASC_KEY_PATH"],
    )
    
    # Fetch certificates and profiles
    sync_code_signing(
      type: "appstore",
      readonly: true,
      app_identifier: app_id
    )
    
    # Update project settings to use match provisioning profile
    update_code_signing_settings(
      use_automatic_signing: false,
      path: "Runner.xcodeproj",
      team_id: team_id,
      profile_name: match_profile,
      code_sign_identity: "iPhone Distribution"
    )
    
    # Increment build number
    increment_build_number(
      build_number: latest_testflight_build_number + 1
    )
    
    # Build the app
    build_app(
      scheme: "development", # Replace with your app's scheme name
      export_method: "app-store",
      export_options: {
        provisioningProfiles: {
          app_id => match_profile
        }
      }
    )
    
    # Upload to TestFlight
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end

How It Works

  • setup_ci: Configures the environment for CI systems
  • app_id/team_id: Gets your app identifier and team ID from Appfile
  • app_store_connect_api_key: Authenticates with App Store Connect using API keys
  • sync_code_signing: Downloads certificates and profiles using match
  • update_code_signing_settings: Applies the correct provisioning profile to your project
  • increment_build_number: Automatically increases build number based on TestFlight’s latest version
  • build_app: Builds your app with the specified scheme and export method
  • upload_to_testflight: Sends your build to TestFlight

Step 9: Deploy to TestFlight

Run the deployment lane to build and upload your app to TestFlight:

fastlane upload_to_testflight_function
fastlane upload_to_testflight_function
fastlane upload_to_testflight_function
fastlane upload_to_testflight_function
fastlane upload_to_testflight_function

Step 10: Verification

After a successful upload, you’ll see a success message in the terminal. Wait a few minutes for Apple to process the build, then check your TestFlight tab in App Store Connect to see your new build.

Troubleshooting

  • Certificate Issues: If you encounter certificate problems, try running fastlane match nuke to remove all certificates and profiles, then re-run fastlane create_certificates_and_profiles.
  • Build Errors: Check your scheme configuration in Xcode to ensure it’s set up correctly.
  • API Key Issues: Verify that your environment variables are set correctly.

Final Structure

Your Fastlane directory should have the following structure:

Resources

Feel free to contact TBR Group and articulate any concept directly with the dev team.

Share your idea with us, and we’ll come up with the best development solution for your case.

Get in touch today