How to automate fastlane in iOS?
Answer
Overview
Fastlane is a powerful open-source tool that automates mobile app deployment. For Flutter developers targeting iOS, Fastlane can handle building, signing, version validation, TestFlight uploads, and even App Store submission โ all with a single command.
If you're tired of manually exporting .ipa files from Xcode and uploading them through App Store Connect, this guide will help you fully automate your iOS release workflow.
Key Benefits:
- โก Automate repetitive tasks - Build, sign, and upload automatically
- โฑ๏ธ Save time - No more manual Xcode exports
- ๐ Prevent duplicate builds - Validates version + build numbers
- ๐ค CI/CD friendly - Works with GitHub Actions, Bitrise, Jenkins
- ๐ Centralized configuration - Everything is version-controlled
Prerequisites
Before setting up Fastlane for iOS, ensure you have:
Apple Requirements:
- โ Active Apple Developer subscription ($99/year)
- โ App has completed initial verification
- โ App has been released at least once on App Store Connect
System Requirements:
- macOS with Xcode installed (latest stable version)
- Ruby 2.5 or higher (check with )text
ruby --version - Homebrew installed (for macOS)
- Flutter SDK installed and configured
Project Requirements:
- Flutter project with iOS folder
- App already registered in App Store Connect
- Bundle identifier configured in Xcode
โ ๏ธ Important: Fastlane requires your app to have completed the first manual release. You cannot use Fastlane for the very first App Store submission.
Step 1: Install & Initialize Fastlane
1๏ธโฃ Install Fastlane
macOS (Recommended):
bashbrew install fastlane
Alternative (RubyGems):
bashsudo gem install fastlane -NV
2๏ธโฃ Navigate to Your Flutter Project
bashcd your_flutter_project cd ios
3๏ธโฃ Initialize Fastlane
bashfastlane init
When prompted:
textWhat would you like to use fastlane for? 1. ๐ธ Automate screenshots 2. ๐ฉโโ๏ธ Automate beta distribution to TestFlight 3. ๐ Automate App Store distribution 4. ๐ Manual setup
Choose Option 3 โ Fully automated deployment
You'll be prompted to:
- Enter your Apple ID (App Store Connect email)
- Enter your password
- Complete 2FA verification if enabled
After initialization, Fastlane will generate:
textios/ โโโ Gemfile โโโ fastlane/ โโโ Appfile โโโ Fastfile
These files will control your entire deployment pipeline.
Step 2: Managing Dependencies (Gemfile Setup)
Open the generated
Gemfileruby# ios/Gemfile source "https://rubygems.org" ruby "~> 3.4.7" gem "fastlane", ">= 2.232.0" gem "cocoapods", ">= 1.16.2" gem "abbrev"
Install dependencies:
bashbundle install
Why use Bundler?
- โ Ensures all developers use the same fastlane version
- โ Prevents "works on my machine" issues
- โ Required for CI/CD consistency
Always run fastlane via Bundler:
bashbundle exec fastlane beta
Step 3: Create Environment Configuration
Create a
.envios/fastlane/bashcd fastlane touch .env
Add the following configuration:
bash# ios/fastlane/.env # App Bundle Identifier APP_IDENTIFIER=com.example.app # Apple Developer Account Email APPLE_ID=your@email.com # App Store Connect Team ID ITC_TEAM_ID= # Developer Portal Team ID TEAM_ID= # App Store Connect API Key APP_STORE_CONNECT_KEY_ID= APP_STORE_CONNECT_ISSUER_ID= APP_STORE_CONNECT_KEY_PATH=./AuthKey.p8
๐ Where to Find These Values
1. APP_IDENTIFIER
Your app's bundle identifier (e.g.,
com.yourcompany.appnameFind it in:
- Xcode: Open โ General โ Bundle Identifiertext
ios/Runner.xcodeproj - Or in โtext
ios/Runner/Info.plisttextCFBundleIdentifier
2. APPLE_ID
Your Apple Developer account email address (used to sign in to App Store Connect)
3. ITC_TEAM_ID (App Store Connect Team ID)
Step-by-step:
- Go to App Store Connect
- Click on your username (top right)
- Select View Membership
- Copy the Team ID (e.g., )text
123456789
4. TEAM_ID (Developer Portal Team ID)
Step-by-step:
- Go to Apple Developer Portal
- Click on Membership (left sidebar)
- Find Team ID under your account details
- Copy the Team ID (e.g., )text
ABC123XYZ
Note:
andtextITC_TEAM_IDare often the same, but not always. Use the correct values from each portal.textTEAM_ID
5. API Key Values (See Step 4 below)
- โ Key ID from App Store Connecttext
APP_STORE_CONNECT_KEY_ID - โ Issuer ID from App Store Connecttext
APP_STORE_CONNECT_ISSUER_ID - โ Path to .p8 file (e.g.,text
APP_STORE_CONNECT_KEY_PATH)text./AuthKey.p8
๐ Secure Your .env File
Add to .gitignore
bash# ios/fastlane/.gitignore .env *.p8
Never commit:
- โ file (contains sensitive credentials)text
.env - โ files (API keys)text
.p8
For CI/CD:
- Use GitHub Secrets, Bitrise Environment Variables, etc.
- Never hardcode credentials in code
Step 4: Create App Store Connect API Key
Why Use API Keys?
- โ More secure - No passwords stored in CI/CD
- โ More reliable - Doesn't expire like app-specific passwords
- โ No 2FA issues - Works seamlessly in automation
- โ Recommended by Apple - Modern authentication method
Create API Key
1. Go to App Store Connect:
Visit: https://appstoreconnect.apple.com
2. Navigate to API Keys:
- Click on Users and Access (in the top navigation)
- Click on Integrations tab (formerly "Keys")
- Click the + button to create a new key
3. Configure the Key:
| Field | Value |
|---|---|
| Name | text |
| Access | App Manager (minimum required) or Admin (full access) |
4. Download the Key:
- Click Generate
- Download the file immediately (โ ๏ธ only downloadable once!)text
.p8 - Note down the Key ID (e.g., )text
ABC123XYZ - Note down the Issuer ID (e.g., )text
12345678-1234-1234-1234-123456789012
5. Store the Key:
bash# Move the downloaded .p8 file to fastlane directory mv ~/Downloads/AuthKey_ABC123XYZ.p8 ios/fastlane/AuthKey.p8 # Secure the file chmod 600 ios/fastlane/AuthKey.p8
6. Update .env file:
bash# ios/fastlane/.env APP_STORE_CONNECT_KEY_ID=ABC123XYZ APP_STORE_CONNECT_ISSUER_ID=12345678-1234-1234-1234-123456789012 APP_STORE_CONNECT_KEY_PATH=./AuthKey.p8
API Key Permissions
| Role | Can Upload to TestFlight? | Can Submit to App Store? |
|---|---|---|
| Developer | โ No | โ No |
| App Manager | โ Yes | โ Yes |
| Admin | โ Yes | โ Yes |
Recommendation: Use App Manager role (least privilege principle).
Step 5: Configure Fastlane Files
๐ Update Appfile
ruby# ios/fastlane/Appfile app_identifier(ENV["APP_IDENTIFIER"]) apple_id(ENV["APPLE_ID"]) itc_team_id(ENV["ITC_TEAM_ID"]) team_id(ENV["TEAM_ID"])
This file:
- Reads values from filetext
.env - Sets default app identifier and team IDs
- Used by all Fastlane lanes
๐ Update Fastfile
Production-ready Fastfile for Flutter iOS automation:
ruby# ios/fastlane/Fastfile default_platform(:ios) # Load environment variables Dotenv.load('.env') platform :ios do # API Key helper method def api_key key_path = File.expand_path(ENV["APP_STORE_CONNECT_KEY_PATH"], __dir__) app_store_connect_api_key( key_id: ENV["APP_STORE_CONNECT_KEY_ID"], issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"], key_filepath: key_path ) end # Lane: Push a new iOS beta build to TestFlight desc "Push a new iOS beta build to TestFlight" lane :beta do require 'yaml' # Read version from pubspec.yaml pubspec = YAML.load_file("../../pubspec.yaml") version, build_number = pubspec['version'].split('+') # Check if build already exists on TestFlight latest_build = app_store_build_number( api_key: api_key, app_identifier: ENV["APP_IDENTIFIER"], live: false, version: version ) rescue nil # Prevent duplicate builds if latest_build && latest_build.to_i >= build_number.to_i UI.user_error!("โ Build #{build_number} already exists on TestFlight. Increment build number in pubspec.yaml.") end # Build Flutter app sh("cd ../.. && flutter build ipa --release") # Build iOS app build_app( scheme: "Runner", workspace: "Runner.xcworkspace", export_method: "app-store", xcargs: "-allowProvisioningUpdates" ) # Upload to TestFlight testflight( api_key: api_key, app_identifier: ENV["APP_IDENTIFIER"] ) UI.success("โ Uploaded to TestFlight successfully ๐") end # Lane: Promote existing TestFlight build to App Store desc "Promote existing TestFlight build to App Store" lane :production do |options| require 'yaml' # Read version from pubspec.yaml pubspec = YAML.load_file("../../pubspec.yaml") version, build = pubspec['version'].split('+') # Promote to App Store deliver( api_key: api_key, app_version: version, build_number: build, submit_for_review: false, # Set to true for auto-submit automatic_release: false, # Set to true for auto-release force: true, skip_metadata: false, skip_screenshots: false ) UI.success("โ Submitted to App Review ๐") end # Lane: Run tests desc "Run iOS unit tests" lane :test do run_tests( scheme: "Runner", device: "iPhone 14 Pro", clean: true ) end # Error handling error do |lane, exception| UI.error("โ Error in lane #{lane}: #{exception.message}") end end
๐ Key Features of This Fastfile
1. Version Validation โ
rubylatest_build = app_store_build_number(...) if latest_build && latest_build.to_i >= build_number.to_i UI.user_error!("Build already exists. Increment build number.") end
Prevents:
- โ Uploading duplicate builds
- โ Wasting build time
- โ App Store Connect errors
2. Reads Version from pubspec.yaml โ
rubypubspec = YAML.load_file("../../pubspec.yaml") version, build_number = pubspec['version'].split('+')
Example pubspec.yaml:
yamlversion: 1.2.0+42
- =text
versiontext1.2.0 - =text
build_numbertext42
3. Flutter Build Integration โ
rubysh("cd ../.. && flutter build ipa --release")
Runs:
bashflutter build ipa --release
Generates:
- text
build/ios/ipa/your_app.ipa
4. Production Lane โ
rubylane :production do deliver( api_key: api_key, app_version: version, build_number: build ) end
Purpose:
- Promotes an existing TestFlight build to App Store
- No need to rebuild
- Just promotes the version you specify
Step 6: Running Fastlane Lanes
๐ Upload to TestFlight
bashcd ios bundle exec fastlane beta
What happens:
- Reads version from text
pubspec.yaml - Checks if build already exists on TestFlight
- Builds Flutter app ()text
flutter build ipa --release - Builds iOS IPA file
- Uploads to TestFlight
- Shows success message
๐ Submit to App Store
bashcd ios bundle exec fastlane production
What happens:
- Reads version from text
pubspec.yaml - Promotes existing TestFlight build to App Store
- Submits for review (if )text
submit_for_review: true
๐งช Run Tests
bashcd ios bundle exec fastlane test
โ ๏ธ Important Notes
Before running Fastlane, ensure:
1. App Exists in App Store Connect โ
- Create your app in App Store Connect first
- Set up app information, screenshots, description
- Configure pricing and availability
2. Complete First Manual Release โ
- Fastlane cannot handle the very first App Store submission
- You must manually release version 1.0.0 through Xcode + App Store Connect
- After first release, Fastlane can handle all future updates
3. Increment Version in pubspec.yaml โ
Before each release:
yaml# pubspec.yaml # WRONG - Same as previous release version: 1.0.0+1 # CORRECT - Incremented build number version: 1.0.0+2 # OR increment version version: 1.1.0+1
Rules:
- Increment build number (+1, +2, +3) for TestFlight builds
- Increment version (1.0.0 โ 1.1.0) for App Store releases
4. Ensure Signing & Certificates are Configured โ
Option 1: Automatic Signing (Recommended for beginners)
In Xcode:
- Open text
ios/Runner.xcworkspace - Select Runner target
- Signing & Capabilities tab
- Enable Automatically manage signing
- Select your team
Option 2: Manual Signing (Recommended for teams)
Use Fastlane Match (see Advanced Setup below)
CI/CD Integration
GitHub Actions Example
Create
.github/workflows/ios-release.ymlyamlname: iOS Release on: push: tags: - 'v*' # Trigger on version tags (e.g., v1.0.0) jobs: release: runs-on: macos-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.16.0' channel: 'stable' - name: Install dependencies run: flutter pub get - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.0' bundler-cache: true - name: Install fastlane run: | cd ios bundle install - name: Create .env file run: | cd ios/fastlane cat << EOF > .env APP_IDENTIFIER=${{ secrets.APP_IDENTIFIER }} APPLE_ID=${{ secrets.APPLE_ID }} ITC_TEAM_ID=${{ secrets.ITC_TEAM_ID }} TEAM_ID=${{ secrets.TEAM_ID }} APP_STORE_CONNECT_KEY_ID=${{ secrets.APP_STORE_CONNECT_KEY_ID }} APP_STORE_CONNECT_ISSUER_ID=${{ secrets.APP_STORE_CONNECT_ISSUER_ID }} APP_STORE_CONNECT_KEY_PATH=./AuthKey.p8 EOF - name: Decode App Store Connect API Key env: APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }} run: | cd ios/fastlane echo "$APP_STORE_CONNECT_API_KEY_CONTENT" | base64 --decode > AuthKey.p8 chmod 600 AuthKey.p8 - name: Build and deploy to TestFlight run: | cd ios bundle exec fastlane beta
Required GitHub Secrets:
| Secret Name | Value |
|---|---|
text | Your app's bundle identifier |
text | Your Apple ID email |
text | App Store Connect Team ID |
text | Developer Portal Team ID |
text | Key ID from API key |
text | Issuer ID from API key |
text | Base64 encoded .p8 file |
To create APP_STORE_CONNECT_API_KEY_CONTENT:
bash# Base64 encode the .p8 file base64 -i AuthKey.p8 | pbcopy # Paste into GitHub Secrets
Advanced: Code Signing with Match
Why Use Match?
Without Match:
- โ Each developer manages their own certificates
- โ "Provisioning profile doesn't match" errors
- โ Manual certificate renewal
- โ Complex CI/CD setup
With Match:
- โ Single source of truth for certificates
- โ Shared across team and CI/CD
- โ Automatic renewal
- โ Zero configuration after setup
Setup Match
1. Create Private Git Repository:
bash# On GitHub, create a private repo: "ios-certificates" # NEVER make this public - contains signing certificates
2. Initialize Match:
bashcd ios fastlane match init
Select git storage and enter repository URL.
3. Generate Certificates:
bashfastlane match appstore
4. Update Fastfile:
Add to your
betarubylane :beta do # Sync certificates match( type: "appstore", readonly: true, api_key: api_key ) # Rest of your lane... end
Troubleshooting
Issue: "Build already exists on TestFlight"
Error:
textโ Build 42 already exists on TestFlight. Increment build number in pubspec.yaml.
Solution:
Update
pubspec.yamlyaml# Before version: 1.0.0+42 # After version: 1.0.0+43
Issue: "Could not find workspace"
Error:
textCould not find workspace Runner.xcworkspace
Solution:
bashcd ios pod install
This generates
Runner.xcworkspaceIssue: "No provisioning profile found"
Error:
textNo provisioning profile matching 'com.example.app' found
Solution:
Option 1: Enable automatic signing in Xcode
Option 2: Use Match
bashfastlane match appstore
Issue: "Authentication failure"
Error:
textCould not authenticate with App Store Connect
Solution:
Verify your
.env- Check text
APP_STORE_CONNECT_KEY_ID - Check text
APP_STORE_CONNECT_ISSUER_ID - Verify file exists attext
.p8textAPP_STORE_CONNECT_KEY_PATH
Best Practices
1. Always Use Bundler โ
bash# WRONG fastlane beta # CORRECT bundle exec fastlane beta
2. Version Control Fastlane Files โ
Commit to Git:
- โ
text
Gemfile - โ
text
Gemfile.lock - โ
text
Fastfile - โ
text
Appfile
Add to .gitignore:
- โ text
.env - โ text
*.p8 - โ text
fastlane/report.xml
3. Increment Version Before Each Release โ
Create a checklist:
markdownBefore release: - [ ] Update version in pubspec.yaml - [ ] Test the app - [ ] Run fastlane beta - [ ] Test on TestFlight - [ ] Run fastlane production
4. Test Locally First โ
Before setting up CI/CD:
- Test locallytext
bundle exec fastlane beta - Verify upload to TestFlight
- Then configure GitHub Actions
Summary
| Task | Command | Description |
|---|---|---|
| Install Fastlane | text | Install via Homebrew |
| Initialize | text | Setup Fastlane in project |
| Install Dependencies | text | Install Ruby gems |
| Upload to TestFlight | text | Build and upload |
| Submit to App Store | text | Promote to App Store |
| Run Tests | text | Run unit tests |
Final Thoughts
With Fastlane configured, your iOS release process becomes:
bash# Update version in pubspec.yaml version: 1.2.0+43 # One command for TestFlight bundle exec fastlane beta # One command for App Store bundle exec fastlane production
Clean. Automated. Production-ready. ๐๐
What You've Automated
โ No more manual IPA uploads โ No duplicate version mistakes โ Version validation from pubspec.yaml โ One command = Full TestFlight release โ One command = Production submission โ CI/CD ready with GitHub Actions
Your Flutter iOS deployment is now fully automated!
Official Documentation: