skip to content

What problem does fastlane's match (sync_code_signing) solve for iOS code signing?

level: juniorimportance: should knowfreq 66%

answer

  1. one shared identity, not per-machine
  2. encrypted repository of signing assets
  3. canonical name: sync_code_signing
  4. keyed by app_identifier and type
  5. Apple only; Android uses a keystore

basics

~20 s

match, whose canonical action name is sync_code_signing, keeps one team's Apple certificates and provisioning profiles in an encrypted shared repository, so every developer machine and CI runner signs the iOS app with the same identity instead of enrolling its own.

solid answer

~40 s

Signing an iOS build needs a private key, its Apple certificate and a matching provisioning profile on the machine running the build. Left to per-machine automation, every laptop enrols its *own* certificate and a fresh CI runner has none at all. `match` — canonically **`sync_code_signing`** — stores those artefacts once, encrypted with OpenSSL, in a backend chosen by `storage_mode` (`git`, `google_cloud`, `s3`, `gitlab_secure_files`), and syncs them down keyed by `app_identifier` and `type` (`appstore`, `adhoc`, `development`, …). For the falconry weight-log app, a laptop and the CI runner then sign with the identical identity. It is Apple-only: Android app signing uses a keystore configured in the Gradle build, not `match`.

code

ruby · 8 lines
ruby
lane :install_appstore_signing do
  sync_code_signing(
    type: "appstore",
    app_identifier: "com.mews.falconry-weight-log",
    storage_mode: "git",
    git_url: "[email protected]:mews/falconry-signing.git"
  )
end

go deeper

for a junior

Be ready to say what match syncs — Apple certificates and provisioning profiles — and that the copy on your laptop and the copy on CI are the same one, pulled from an encrypted shared repository.

for a middle

Explain the mechanics: assets are keyed by app_identifier and type, encrypted client-side with OpenSSL, then installed into a keychain and Xcode's profile location before the build step runs.

for a senior

Show you can operate it: who holds write access, who runs read-only, how a new joiner or a fresh runner is onboarded, and how the team's limited certificate slots stay under control.

for a principal

Own the policy: one signing repository per team or per app, who may create identities, and what the blast radius is when that shared vault or its passphrase leaks.

## Why iOS code signing is shared team state Every iOS build must be signed before a device or the App Store will accept it, and signing needs three things present on the machine that runs the build: a **private key**, the **Apple-issued certificate** that names that key, and a **provisioning profile** binding the certificate, the app's bundle identifier and — for anything distributed outside the App Store — a list of registered devices. None of it lives in the application repository, and the private key cannot be regenerated: lose it and the certificate that names it is scrap. Left to per-machine automation, every laptop enrols its own certificate. Six engineers on the falconry weight-log app end up holding six distribution identities against a quota that is deliberately small, a drawer of profiles nobody can reproduce, and a CI runner that begins each job with an empty keychain and cannot sign at all. The certificate on whichever machine happened to cut the last release becomes load-bearing, undocumented state. ## What sync_code_signing actually does `match` is the nickname; **`sync_code_signing`** is the canonical fastlane action, and the canonical name is worth using because it describes the behaviour: this is a *sync*, not a per-machine generator. A run does the following. 1. **Opens the storage backend** named by `storage_mode` — one of `git`, `google_cloud`, `s3` or `gitlab_secure_files`. With Git that means cloning `git_url`, optionally at `git_branch` and with `shallow_clone`. 2. **Decrypts the contents.** match encrypts client side with **OpenSSL**, so the backend only ever holds ciphertext and the passphrase is what turns it back into usable keys. 3. **Looks for assets matching `app_identifier` and `type`.** If they exist it installs them. If they do not, and the run is not `readonly`, it creates them through the Apple Developer Portal and writes the new material back, encrypted. 4. **Installs what it found.** The private key and certificate go into a keychain — `keychain_name` and `keychain_password` select and unlock it — and the provisioning profiles are written where Xcode expects them. 5. **Publishes the result into the lane context**, including `SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING`, so the later export step can map each target to the profile it should use. The outcome is the property the tool exists for: your laptop and the CI runner install the *same* identity instead of each inventing one. ## Assets are keyed by app_identifier and type The repository is not one undifferentiated blob. Material is filed by the app it belongs to (`app_identifier`) and by what it is for (`type`), and `type` is a closed list of seven values: - `appstore` — the distribution identity for builds destined for App Store Connect. - `adhoc` — distribution to a fixed list of registered devices, so this is the profile that goes stale the moment a tester's iPhone is added. - `development` — everyday builds onto developers' own devices. - `enterprise` — in-house distribution outside the store. - `developer_id`, `mac_installer_distribution` and `developer_id_installer` — the macOS side of the family. One repository can therefore hold the weight-log app's `appstore` and `adhoc` material side by side, and other apps' material alongside it, without collisions. ## iOS and Android are not symmetric here This is the part candidates most often get wrong, because fastlane itself is cross-platform while this tool is not. | | iOS / Apple | Android | |---|---|---| | identity | private key plus an Apple-issued certificate | private key inside a keystore file | | extra artefact | provisioning profile, bound to bundle id and devices | none; there is no per-device profile | | issuing authority | the Apple Developer Portal, which can revoke | you, self-signed, with nobody to revoke it | | kept in sync by match | yes — this is the whole job | no | | where it is configured | the signing lane, consumed by the export step | the Gradle build's signing configuration | The divergence is not an omission. Android has no portal-issued certificate and no per-device profile to reconcile, so there is nothing for a sync tool to reconcile; the keystore is a single file whose handling belongs to the Android build configuration rather than to `sync_code_signing`. ## Where match's job ends - It does not build or export. The export step consumes the profile match installed and produces the artifact. - It does not upload anything to a store or a beta service. - It does not decide where the encryption passphrase or the backend credentials live in your pipeline; that is a CI secrets question, not a match option. Knowing those edges is most of the value of this answer in an interview: match owns the *identity*, and everything downstream merely consumes it.

  • Why does match encrypt the repository contents rather than relying on the storage backend's access control?
    Because the material is a private key: anyone who can clone the repo or read the bucket could otherwise sign as your team. match encrypts client-side with OpenSSL, so `git`, `s3`, `google_cloud` and `gitlab_secure_files` only ever hold ciphertext, and access control becomes a second layer rather than the only one.
  • What does match leave behind on the machine once it has decrypted the assets?
    The private key and its certificate are imported into a keychain — `keychain_name` and `keychain_password` choose and unlock it — and the provisioning profiles are written where Xcode looks for them. match also publishes the profile mapping into the lane context as `SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING`, so the export step can consume it.

It is a password manager for signing material: one encrypted vault that every machine unlocks, instead of each machine minting its own entry.

saying these in an interview costs you the question

  • Says match generates a fresh certificate on every machine
  • Claims match also syncs the Android upload keystore
  • Thinks the signing repository is stored unencrypted
  • Knows only the nickname, never sync_code_signing
  • Believes match builds or uploads the app as well