Alle artikelen
15 oktober 2026

Fastlane for iOS CI/CD: automating builds, signing and submission

Fastlane turns the archive, sign and upload loop into named lanes your CI can run. Here is the setup that works: Bundler, an API key, match and pilot.

Hands typing on a laptop keyboard at a desk, a calm developer workspace for scripting iOS build automation

Introduction

Fastlane automates the three jobs that slow iOS releases down: building the app, signing it and getting it into App Store Connect. You describe each job as a lane in one Fastfile, and your CI runs the lane the same way every time. This guide covers the setup in the order you will hit it. It reflects the fastlane documentation as of October 2026.

Manual releases fail in predictable ways. The certificate lives on one laptop. The upload needs someone's Apple ID. Nobody remembers the export settings. A lane removes all three. If you are still sizing the project, what an iOS app costs to build covers where release tooling fits in the budget.

Install and structure the project

Install fastlane with Bundler. The fastlane setup guide names it the preferred method: add a Gemfile with gem "fastlane", run bundle update, commit both the Gemfile and Gemfile.lock, and call lanes with bundle exec fastlane [lane]. Committing the lockfile pins the fastlane version, so your laptop and your CI runner behave the same.

Homebrew and system Ruby also work. The guide advises against system Ruby because of file permission problems. It states that fastlane supports Ruby 3.2 or newer and prefers 3.3 or greater.

Run fastlane init and fix the locale

Run fastlane init in the project directory. It creates the fastlane folder, and fastlane/Fastfile holds everything needed to distribute the app. Each lane is a named block you call from the command line or from CI.

Fastlane also needs a UTF-8 locale. Set LC_ALL and LANG to en_US.UTF-8. The fastlane CI guide warns that many CI systems default to an unset or ASCII locale, so verify it on the runner.

Authenticate with an App Store Connect API key

Use an App Store Connect API key instead of an Apple ID and password. The fastlane API key guide says the key avoids two-factor prompts, performs better and relies on a documented API. A CI runner cannot answer a 2FA prompt, so this choice decides whether unattended runs work at all.

Create the key

In App Store Connect, open Users and Access, then the Integrations tab, and create a team key. Note the issuer ID and download the .p8 file immediately. It cannot be downloaded again after the page is refreshed. Use a team key: individual keys cannot reach the provisioning endpoints that match needs.

Load it in the Fastfile

The app_store_connect_api_key action takes key_id, issuer_id and key_filepath, plus an optional duration of up to 1200 seconds. Call it without assigning the result and it stores the key in the lane context, so later actions load it on their own. Pilot, deliver, sigh, cert, match, download_dsyms and app_store_build_number all support API keys. Produce and pem do not.

Store the key content and IDs as CI secrets. Never commit the .p8 file.

Code signing with match

Match keeps your signing identity out of individual machines. Per the match documentation, it stores certificates and profiles encrypted in a private git repo (the default), Google Cloud Storage or Amazon S3. The type option takes development (the default), appstore, adhoc or enterprise. Set MATCH_PASSWORD as a secret so the decryption step never prompts.

Switching to match means revoking your existing certificates first, as the fastlane code signing guide notes. Plan it for a quiet week, and read the APNs setup article first if push certificates are part of your profiles.

Use readonly and setup_ci on CI

On CI, run match with readonly set to true. It then fetches existing certificates and profiles and never generates new ones, so a broken run cannot create a mess in your developer account. The docs show match(type: "appstore", readonly: is_ci).

Put setup_ci at the top of the Fastfile. Per the setup_ci docs, it creates a temporary keychain (named fastlane_tmp_keychain by default, unlocked for 3600 seconds by default) and switches match to readonly mode. The temporary keychain keeps signing identities off the runner's login keychain.

One practical catch from the match docs: GitHub does not allow the same deploy key on multiple repos. If several projects share a certificates repo, use a separate read-only account.

Build and ship to TestFlight

Two actions do the work. build_app (also called gym) archives the app with xcodebuild, exports it and returns the path to the signed ipa. Per the build_app docs, the scheme should be marked Shared, and export_method takes values such as app-store, ad-hoc and development. The output_directory defaults to the current directory.

A minimal beta lane looks like this:

lane :beta do
  setup_ci
  app_store_connect_api_key(
    key_id: ENV["ASC_KEY_ID"],
    issuer_id: ENV["ASC_ISSUER_ID"],
    key_filepath: ENV["ASC_KEY_PATH"]
  )
  match(type: "appstore", readonly: true)
  build_app(scheme: "App", export_method: "app-store")
  upload_to_testflight(changelog: "Build from main")
end

Replace the scheme and variable names with your own. The upload_to_testflight action (pilot) sets the "What to Test" text with changelog. Its skip_waiting_for_build_processing option, false by default, matters when CI minutes are billed. The pilot docs say that when it is true no build is distributed to testers and distribute_external stops working. With a changelog set, it waits only until the build appears so the changelog can be attached.

Submit with deliver

Deliver, also called upload_to_app_store, uploads metadata, screenshots, previews and binaries. The deliver docs say submit_for_review defaults to false, so a lane never submits by accident. Set it to true when you want the lane to submit the latest build after the upload.

Two more defaults matter here. automatic_release controls whether the app goes live for all users once Apple approves it. And run_precheck_before_submit defaults to true, so deliver runs precheck for common rejection causes before submitting. Precheck's default rule level is :warn.

Pass force set to true on CI. Without it, deliver shows an HTML preview and waits for confirmation.

Wiring lanes into CI

Fastlane works with GitHub Actions, GitLab CI, CircleCI, Bitrise, Codemagic and Jenkins, among others. The pattern is the same everywhere: check out the code, run bundle install, export the secrets as environment variables and call bundle exec fastlane beta.

Keep the secrets list short. You need the API key ID, issuer ID and key content, plus MATCH_PASSWORD. Set LANG and LC_ALL on the job.

Do not release to the App Store on every commit. The fastlane CI guide points out that review can take 1 to 2 days, and recommends git tags or a manually triggered release job instead. A workable split is TestFlight on every merge to main and a tagged release lane for deliver. Treat build duration like any other budget, the way you would enforce budgets in CI for a web project.

Conclusion

Start with Bundler and a committed lockfile, authenticate with an API key, let match own signing in readonly mode, and split TestFlight uploads from App Store submissions. That gives you a release pipeline that does not depend on one person's laptop. If you want a second pair of hands on the pipeline, our iOS development team sets this up as part of app delivery.

Frequently asked questions

Do I need an Apple ID password to run fastlane on CI?

No. An App Store Connect API key (key ID, issuer ID and the .p8 file) authenticates pilot, deliver, match and several other actions without 2FA prompts. Produce and pem do not support API keys.

What does match readonly mode do?

Readonly mode fetches existing certificates and profiles and never generates new ones. That is the safe setting for CI, and setup_ci turns it on for you.

Does deliver submit my app for review automatically?

Only if you set submit_for_review to true. The default is false, and deliver runs precheck before submitting unless you turn that off.

Should every commit go to the App Store?

No. Review can take 1 to 2 days, and fastlane advises releasing from a git tag or a manual job. Send every merge to TestFlight and keep the App Store lane for deliberate releases.