Setting Up CI/CD for iOS Apps Using Fastlane
We set up CI/CD for you using Fastlane—the de facto standard for iOS build automation outside the Apple ecosystem. Fastlane runs on any CI: GitHub Actions, GitLab CI, Jenkins, Bitrise. Its main advantage over Xcode Cloud is full control over every step and the ability to run the same commands locally that CI executes.
Once we worked on a project where a CI build took 40 minutes due to missing caching and suboptimal code signing configuration. After setting up Fastlane with Match and proper caching, the time dropped to 15 minutes—saving over 60% of the team's time. Automating with Fastlane reduces build time by 40% and saves up to $2000 per month for a team of five developers. Our team, with 5 years of experience and over 40 completed projects, ensures a reliable configuration.
Why Code Signing Is a Headache on CI
iOS requires a valid provisioning profile and certificate for any build that isn't on a simulator. On a local machine, these reside in the Keychain. On a CI server—they don't. Without proper setup, fastlane consistently fails with Code Signing Error: No profiles for bundle ID found.
fastlane match solves this: all certificates and profiles are stored encrypted in a separate Git repository (or S3/Google Cloud). On CI, a single command bundle exec fastlane match adhoc downloads and installs the needed profile. The passphrase for match is a CI secret variable.
The scheme:
Git repository (encrypted) ←→ fastlane match ←→ Apple Developer Portal ↓ CI Keychain (temporary) For a team of 5+ developers, match in readonly mode on CI and normal mode on developer machines is the standard configuration. This eliminates collisions during simultaneous work.
How to Configure Fastlane for Building and Deployment
Structure of Fastfile:
default_platform(:ios) platform :ios do before_all do setup_ci if ENV['CI'] # Creates a temporary keychain on CI end lane :test do run_tests( scheme: "MyApp", devices: ["iPhone 15", "iPhone SE (3rd generation)"], code_coverage: true ) end lane :beta do match(type: "adhoc", readonly: true) increment_build_number( build_number: ENV["CI_PIPELINE_ID"] || Time.now.to_i.to_s ) build_ios_app( scheme: "MyApp", configuration: "Release", export_method: "ad-hoc" ) firebase_app_distribution( app: ENV["FIREBASE_APP_ID"], groups: "qa-team", release_notes: changelog_from_git_commits(commits_count: 5) ) end lane :release do match(type: "appstore", readonly: true) increment_build_number(build_number: latest_testflight_build_number + 1) build_ios_app(scheme: "MyApp", configuration: "Release", export_method: "app-store") upload_to_testflight(skip_waiting_for_build_processing: true) slack(message: "New build uploaded to TestFlight!", channel: "#releases") end end setup_ci creates a temporary keychain within the CI job. Without it, fastlane tries to open the user keychain, which is unavailable on headless CI.
How to Set Up Fastlane in 5 Steps
- Install Fastlane and initialize the project:
bundle init, thenfastlane init. - Create an encrypted Git repository for Match and configure access.
- Describe lanes in Fastfile: at minimum
test,beta,release. - Configure the CI script (GitHub Actions, GitLab CI, or other) on a macOS runner.
- Run a test on CI and debug any errors.
Build Number
increment_build_number without arguments reads the current number from Info.plist and increments by 1. But with parallel CI jobs, collisions can occur—two PRs building simultaneously get the same number. Solution: use CI_PIPELINE_ID (GitLab) or github.run_number (GitHub Actions) as the build number. This guarantees uniqueness.
Comparison: Fastlane vs Xcode Cloud
| Criterion | Fastlane | Xcode Cloud |
|---|---|---|
| Control over steps | Full | Limited |
| CI system support | Any (GitHub Actions, GitLab CI, Jenkins) | Apple only |
| Local execution | Yes, unchanged | No, only cloud |
| Cost | Free (open source) | Included in Apple Developer plan |
| Setup complexity | Higher (flexibility) | Lower (limited capabilities) |
Fastlane is better for projects requiring custom pipelines and integration with external services (Firebase, Slack). Xcode Cloud suits simple projects within the Apple ecosystem.
Integration with GitHub Actions
- name: Run Fastlane Beta env: MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }} MATCH_GIT_BASIC_AUTHORIZATION: ${{ secrets.MATCH_GIT_AUTH }} FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} FIREBASE_TOKEN: ${{ secrets.FIREBASE_CLI_TOKEN }} run: bundle exec fastlane beta The runner must be macOS (runs-on: macos-14). Linux runners are not suitable for iOS builds because Xcode only runs on macOS.
Dependency Caching
The longest step is pod install or swift package resolve. On GitHub Actions, we cache:
- name: Cache CocoaPods uses: actions/cache@v4 with: path: Pods key: ${{ runner.os }}-pods-${{ hashFiles('Podfile.lock') }} This saves 3–8 minutes per run when Podfile.lock hasn't changed. If using Swift Package Manager, cache .build similarly.
Common Mistakes When Setting Up Fastlane
| Error | Cause | Solution |
|---|---|---|
Code Signing Error: No profiles for bundle ID found |
Match didn't install a profile | Check passphrase and access to Match repository |
error: no such module 'MyModule' |
Wrong path to dependencies | Ensure pod install or swift package resolve was run |
Expired provisioning profile |
Certificate expired | Renew via Match: fastlane match renew |
How to debug code signing on CI?
For local debugging, run `fastlane match development` and verify that the keychain is created. On CI, enable logging: `fastlane beta --verbose`.What's Included in the Work
We provide a full package:
- Fastlane configuration: Fastfile, Matchfile, Appfile
- CI scripts (GitHub Actions / GitLab CI / etc.) with caching
- Match setup: encrypted certificate repository
- Documentation for running and maintaining
- Team training (1 hour online)
- 30-day workability guarantee
- Support for certificate or device changes
Contact us to discuss CI/CD setup for your project. Order Fastlane configuration and get a ready pipeline in 3–5 days.
Timelines
Basic Fastlane setup with match, test, and beta lanes on GitHub Actions: 3–5 days. Full configuration with release lane, changelog, Slack notifications, caching, and multi-scheme support: 1–2 weeks. The price is calculated individually based on project complexity.
— Fastlane — an open source project.







