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.
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
# Navigate to your iOS project directory
cd ios
# Install Fastlane using Ruby's gem package manager
gem install fastlane
# Initialize Fastlane in your project
# 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
Fastlane will ask what you want to use it for. For TestFlight deployment, select the option for “Automate beta distribution to TestFlight.”
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
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
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
1. To automate the deployment process, you’ll need to set up API access to App Store Connect:
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:
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.
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
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
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
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.
fastlane match nuke
to remove all certificates and profiles, then re-run fastlane create_certificates_and_profiles
.Your Fastlane directory should have the following structure: