A practical walkthrough for building, signing, and pushing your iOS app to TestFlight automatically using Fastlane's build_app and pilot actions.
TestFlight is Apple's beta distribution service, and getting builds there manually means archiving in Xcode, exporting, and uploading through the Organizer or Transporter each time. That gets old fast.
With Fastlane, a single lane bumps your build number, compiles and signs the app, and uploads it to TestFlight. The action responsible for the upload is pilot, exposed as upload_to_testflight.
By the end of this guide you will have a repeatable beta lane you can run locally and later move to CI.
This assumes you already have Fastlane initialized in a working native iOS project. If you have not set that up yet, do the initialization first, because the steps below build directly on top of it.
TestFlight uploads go through App Store Connect, so Fastlane needs to authenticate. The most reliable method is an App Store Connect API key, which you generate in App Store Connect under Users and Access.
An API key avoids two-factor authentication prompts, which is essential for unattended CI runs. You provide the key's issuer ID, key ID, and the key file contents to Fastlane, typically via the app_store_connect_api_key action or environment variables.
Using an Apple ID and password is possible for local runs, but it triggers two-factor prompts and session expiry headaches.
Prefer the API key approach from the start. Setting it up once saves you from migrating away from a fragile Apple ID flow later.
Store the key ID, issuer ID, and the private key file outside your repository. On a local machine a secrets manager or an untracked file works; on CI, encrypted secrets are the right home for all three values.
TestFlight requires a distribution-signed build. If you use Match, run the appropriate lane to fetch your App Store distribution certificate and provisioning profile before building.
A common pattern is to call match with the appstore type at the start of your beta lane so signing assets are guaranteed present on whatever machine runs it.
If signing is misconfigured, the build step will fail before upload.
Resolve signing first. It is far easier to debug a signing error in isolation than one buried inside a full release run, and getting it right once means every later run inherits a known-good configuration.
Remember that TestFlight and App Store builds both use the same App Store distribution signing. If you have already set up Match with the appstore type for one, the other is covered by the same assets.
Every TestFlight upload needs a unique build number for a given version. Reusing a build number causes App Store Connect to reject the upload.
Fastlane offers actions like increment_build_number, or you can derive the build number from your CI's build counter or a timestamp. On CI, deriving it from the pipeline run number is a clean, collision-free approach.
Decide on one strategy and apply it consistently.
Mixing manual and automatic build numbers is a frequent cause of duplicate-build rejections. Pick a single source of truth for the build number and let every lane use it.
The build_app action, historically called gym, wraps xcodebuild to archive and export a signed .ipa. Point it at your scheme and, if needed, your workspace or project.
For App Store and TestFlight builds, ensure the export method is set to app-store. build_app produces the .ipa that the upload step will send.
Run the build step on its own first if you are unsure it works.
A successful build_app run that outputs a signed .ipa means you are ready to upload. Verifying the artifact exists and is signed before you attempt the upload keeps failures easy to localize.
If build_app cannot determine your export options automatically, pass them explicitly. Being specific about the export method and the signing style removes ambiguity and makes the same lane behave identically on every machine.
With a signed .ipa in hand, call upload_to_testflight. By default it uploads the most recently built .ipa and submits it to TestFlight for processing.
You can configure whether to distribute to external testers, add release notes via changelog, and control whether Fastlane waits for Apple's processing to finish. Waiting is useful when a later step depends on the build being ready.
Apple still needs time to process the build server-side after upload. Fastlane can poll for this, but processing time is outside its control.
Build that expectation into any automation that acts on the uploaded build. Assuming a build is instantly available after upload is a common source of flaky pipelines.
External tester distribution can also require a review step from Apple the first time a build goes out to them, whereas internal testers generally receive builds sooner. Factor that difference into how quickly you expect testers to see a given build.
Now combine the pieces into a single beta lane: authenticate with the API key, fetch signing via Match, bump the build number, run build_app, then run upload_to_testflight. Running fastlane beta should execute all of it in order.
Keep the lane readable. Each action on its own line with clear parameters is easier to maintain than a densely packed block.
Test the complete lane locally at least once before relying on it.
A local success gives you confidence that the only new variable on CI is the environment, not your lane logic. That distinction makes the first CI run far easier to reason about if something goes wrong.
On CI, supply the API key and Match credentials as encrypted secrets rather than files in the repo. Use a read-only Match key on CI so the pipeline can fetch signing assets without being able to modify them.
Trigger the beta lane on the event that suits your workflow, such as a merge to your release branch or a pushed tag. Ensure your CI runner uses macOS with the right Xcode version installed.
With that, every qualifying change can produce a TestFlight build automatically.
Keep an eye on the first few CI runs, since environment differences, not lane logic, cause most initial failures. Once those settle, the pipeline tends to be stable until an Xcode or certificate change forces an update.
The pilot action, exposed as upload_to_testflight. It sends your signed .ipa to App Store Connect for TestFlight distribution.
It is strongly recommended, especially on CI, because it avoids two-factor authentication prompts. Apple ID and password authentication works locally but is fragile for automation.
Almost always because the build number was reused for that version. Ensure each upload has a unique, incrementing build number.
Apple processes builds server-side after upload, and that time is outside Fastlane's control. Fastlane can wait and poll, but processing duration varies.