Post

iOS Certificates, Identifiers & Profiles Explained|Fastlane Match for Unified Certificate Management in CI/CD

Discover how to streamline iOS certificate issuance and management using Fastlane Match, integrating Certificates, Identifiers & Profiles seamlessly into your CI/CD pipeline for efficient app deployment.

iOS Certificates, Identifiers & Profiles Explained|Fastlane Match for Unified Certificate Management in CI/CD

This post was translated with AI assistance — let me know if anything sounds off!

Table of Contents


What are iOS Certificates, Identifiers & Profiles and Some Notes on Unified Management of Certificates and CI/CD with Fastlane Match

Introduction to the relationship between Certificates, Identifiers & Profiles and using Fastlane Match to centrally manage issued certificates and integrate them into the CI/CD workflow.

Photo by [marcos mayer](https://unsplash.com/@mmayyer?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText){:target="_blank"}

Photo by marcos mayer

Preface

In mid-2025, a series of articles was written on building a complete App CI/CD process from scratch using GitHub Actions:

Recently, I set up everything again in a new environment and learned new things each time. This time, I focused on iOS Codesigning: the relationship between Certificates, Profiles, and Devices, and how I use Fastlane Match to manage certificates and integrate with CI/CD.

What are Certificates, Identifiers & Profiles, and Devices?

In the Apple ecosystem, app development is based on certificate and profile management, unlike Android where having an .apk file is enough to install and use. Apple enforces strict certificate matching rules; if these are not met, the app cannot be installed or used.

Main Components and Their Functions:

Certificates: The identity used to sign the App (Signing Identity)

  • Development — Used on physical devices during the development phase (no certificates needed for simulators), belongs to a person or API Key (with quantity limits).

  • Distribution — Packaging for App Store, TestFlight, or Ad Hoc internal testing (limited to registered devices), with quantity limits, belongs to the Team.

  • Enterprise — For internal use within the enterprise.

Format : Requires both Private Key and Certificate .cer to use, or exporting from the original creation computer’s Keychain as a .p12 file will include both.

Identifiers: Which App/Extension (Bundle ID)

App’s Bundle ID registration and the required enabled Capabilities (such as Push Notifications, App Groups…) and App Services.

Extensions also have their own Identifier.

Devices: Registered Devices (iPhone/iPad, etc.)

Development running on physical devices and Distribution Ad Hoc internal testing can only be used on registered devices.

Limit: 100; including cancellations, the quota resets only at the start of each annual billing cycle.

Provisioning Profile (hereafter Profile):

Combination of Certificates + Identifiers + Devices Relationships.

  • Development — The provisioning profile used during the development phase. It is required for running tests on physical devices. The profile includes the relationship between Certificates, Identifiers, and Devices.

  • Ad Hoc — A provisioning profile for internal testing (e.g., Deploy to Firebase App Distribution), containing the relationship between Certificates, Identifiers, and Devices.

  • App Store — The provisioning profile used for packaging and uploading to the App Store / TestFlight, containing the relationship between Certificates and Identifiers.

Format: .mobileprovision

Note: A Profile is just a description file that defines relationships; it does not contain the actual certificate.

Summary

Based on the above, to build on a clean machine without logging into the Xcode Apple Account for a physical device (Development Certificate) or to perform Archive packaging (Distribution Certificate), you must have two files:

  • .mobileprovision Provisioning Profile: Describes the relationship between Certificates, Identifiers, and Devices.

  • .p12 Certificate: The physical certificate (exported from the original computer where the certificate was created).

Additionally, for macOS ≥ 15, the Keychain has moved to:

1
open /System/Library/CoreServices/Applications/Keychain\ Access.app

Looked for a long time…

Common Errors

⚠️⚠️⚠️ Certificates and Profiles are confirmed correct but errors still occur:

Most likely, you still have leftover or multiple certificates from before, causing Xcode to get confused.

This issue is very common!

  1. Close Xcode

  2. Open macOS keychain: open /System/Library/CoreServices/Applications/Keychain\ Access.app

  3. login keychain -> All items -> search apple development -> delete all Certificates

  4. Finder -> Go -> Go to -> ~/Library/MobileDevice/Provisioning\ Profiles -> Delete all Profiles files

  5. Re-pull Certificates

  6. Restarting Xcode should fix the issue.

No signing certificate “iOS Distribution” found / No signing certificate “iOS Development” found:

No "iOS Distribution" signing certificate matching team ID "" with a private key was found.
No "iOS Development" signing certificate matching team ID "" with a private key was found.

Reason:

  • Missing iOS Distribution/iOS Development Certificate

  • Have an iOS Distribution/iOS Development Certificate but no corresponding Private Key

Solution:

  • Delete all old Certificates and Profiles from the Keychain

  • Find the certificate in the Keychain of the computer where the Certificate was originally created, export it as a .p12 file, and install it on the problematic computer.

  • Go to Certificates, Identifiers & Profiles to revoke the old Certificate and generate a new one
    (Don’t worry, this won’t affect the live app; it only impacts development and packaging stages)

Provisioning profile “” doesn’t include signing certificate “Apple Development: XXX”. / Provisioning profile “” doesn’t include signing certificate “Apple Distribution: XXX”.:

Reason:

  • The currently selected Provisioning Profile does not match the current Certificate.

Solution:

  • Delete all old Certificates and Profiles from the Keychain

  • Go to Certificates, Identifiers & Profiles Profiles to ensure the profile has the corresponding certificate checked or regenerate the Profile for use.

No profile for team ‘’ matching ‘’ found: Xcode couldn’t find any provisioning profiles matching ‘’.:

Reason:

  • Specified Provisioning Profile Not Found

Solution:

Provisioning profile “” has app ID “”, which does not match the bundle ID “”. / Provisioning profile doesn’t match the bundle identifier:

Reason:

  • Provisioning profile does not include the current Bundle Identifier

Solution:

Provisioning profile “” doesn’t include the currently selected device “” (identifier ).:

Reason:

  • Provisioning profile does not include the selected physical device’s Device Identifier

Solution:

Could not create another Development/Distribution certificate, reached the maximum number of available Development/Distribution certificates. :

Reason:

Indicates that the maximum number of Development/Distribution Certificates has been reached.

Solution:

Xcode certificates are correct and packaging works fine, but signing errors occur when running packaging commands via CLI (Fastlane):

Reason:

Here I also ran into another issue: I foolishly placed the project in the iCloud sync folder, and for some reason, Fastlane kept showing certificate problems (suspected keychain access issues).

Solution:

Simply move out of the iCloud sync directory.

Common Issues in Daily Use

Development Certificate

  • Before adopting Match for unified certificate management, each developer creates their own Development Certificate and Development Profile; assuming there are 1,000+ developers in the organization, the Certificates, Identifiers & Profiles, Devices backend becomes extremely chaotic and unmanageable.

  • If you have an outsourced team responsible only for development, you still need to add them to the Apple Developer Program backend so they can create their own development certificates and profiles.

Distribution Certificate

  • Distribution Certificates are created by the Team, so each developer program Team can only create a limited number of distribution certificates.

  • A common practice is for one engineer to create the Distribution Certificate and export the .p12 file to other developers who need to publish the app or to use it on the CI/CD machines.

  • When the team is large and there are many apps, sending certificates back and forth becomes very troublesome, and they need to be renewed every year.

  • When packaging Ad Hoc, if a new device is registered, everyone and the CI/CD must re-download the Profile for the new device to take effect.

Fastlane Match

Based on the above issues, we want a platform to manage everything related to certificates on our behalf. All developers and CI/CD services will pull and update data uniformly from this platform. The storage of this platform must be secure. This is — Fastlane Match.

Easily sync your certificates and profiles across your team

A new approach to iOS and macOS code signing: Share one code signing identity across your development team to simplify your code signing setup and prevent code signing issues.

match is the implementation of the codesigning.guide concept. match creates all required certificates & provisioning profiles and stores them in a separate git repository, Google Cloud, or Amazon S3. Every team member with access to the selected storage can use those credentials for code signing. match also automatically repairs broken and expired credentials. It’s the easiest way to share signing credentials across teams

Fastlane Match:

  • Interact with App Store Connect (via App Store Connect API or Apple Developer Login Session) to generate or update certificates

  • Encrypt and upload the certificate files (.mobileprovision Profiles, .p12 Certificate, .cer Certificate) to the Git Repo (or other storage).
    The .cer Certificate is also stored separately, as this allows verification of the certificate’s validity.

  • If you want to divide permissions, you can use two Repos: one for managing Development certificates and another for Distribution certificates.

Folder structure:

  • The certs folder contains all certificates with their private keys

  • The profiles folder contains all provisioning profiles

<https://docs.fastlane.tools/actions/match/>{:target="_blank"}

https://docs.fastlane.tools/actions/match/

Encryption Algorithm: AES-256-GCM

Developers, CI/CD Services:

  • Use the Fastlane match command to manage certificates consistently.

  • Fastlane Match will first fetch the certificate files from the Git repo and decrypt them for use. If it detects expired or unusable certificates and has Create/Write permissions, it will automatically regenerate and push them back to the repo; if it only has Read permission, it will report an error.

Create/Write:

  • Only the person responsible for managing certificates can generate, update, and push certificates.

  • Important Distribution Certificates should be stored in a separate repo accessible only by CI/CD services or administrators

Read:

  • Other developers and CI/CD services have read-only access to pull certificates.

  • The CI/CD service pulls the latest certificates before each task execution.

  • Everyone shares the same Development & Distribution Certificate.

  • After personnel changes, access to the Match Repo will be lost. You can revoke the old certificates and generate new ones. Others just need to pull again (if integrated with a make project script, it’s even smoother).

Fastlane Match Setup and Usage

Taking Git Storage as an example, all certificates.

1. Create an Empty Git Private Match Repo for Storing Certificates
Although all Certificates and Profiles are stored encrypted, you still need to set proper access permissions for the repo.

  1. Confirm that Git SSH access permissions are set up locally. You can use git clone [email protected]:xxx/certificates.git to verify.

It is recommended to use SSH Git Clone Repo consistently, as CI/CD will use the same method.

If running fastlane match gets stuck at If cloning the repo takes too long, you can use the clone_branch_directly option in match., it is most likely an SSH permission issue.

  1. Run bundle exec fastlane match init in the project directory to complete the setup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[21:54:32]: fastlane match supports multiple storage modes, please select the one you want to use:
1. git
2. google_cloud
3. s3
4. gitlab_secure_files
# Enter 1 to use git
?   1

[22:04:40]: Please create a new, private git repository to store the certificates and profiles there
[22:04:40]: URL of the Git Repo: git@github.com:xxx/certificates.git
# Enter the SSH URL of the Git Private Match Repo you created

[22:04:47]: Successfully created './fastlane/Matchfile'. You can open the file using a code editor.
[22:04:47]: You can now run `fastlane match development`, `fastlane match adhoc`, `fastlane match enterprise` and `fastlane match appstore`
[22:04:47]: On the first run for each environment it will create the provisioning profiles and
[22:04:47]: certificates for you. From then on, it will automatically import the existing profiles.
[22:04:47]: For more information visit https://docs.fastlane.tools/actions/match/
  1. After completion, a fastlane/Matchfile : will be generated
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Your Git Private Match Repo SSH URL
git_url("[email protected]:xxxx/certificates.git")

storage_mode("git")

type("development") # The default type, can be: appstore, adhoc, enterprise or development

# app_identifier(["tools.fastlane.app", "tools.fastlane.app2"])
# username("[email protected]") # Your Apple Developer Portal username

# For all available options run `fastlane match --help`
# Remove the # at the beginning of the line to enable other options

# The docs are available on https://docs.fastlane.tools/actions/match

5. Generate App Store Connect API .p8 Key, use the API Key uniformly to create certificates:

⚠️️️️ App Store Connect API .p8 Key has extensive permissions. It can manage certificates, app submissions and reviews, as well as backend users, reviews, and financial reports.

Whether to include it in the team’s .git for everyone to access can be decided based on the team’s situation.

A higher security approach is to allow only the managers to handle it and store it encrypted for use in CI/CD (introduced later in the article).

For demo convenience, it is placed directly under the fastlane directory for access.

Also, the Fastlane script in this article is simplified for demonstration purposes and does not consider code duplication or structure.

Certificate Management

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
platform :ios do
  lane :match_development do \\|options\\|
    # Replace with your App Identifier ID
    app_identifier = "li.zhgchg.myApp"

    type = options.fetch(:type, "development")
    isRead = options.fetch(:isRead, true)
    if isRead 
      readonly = true
      force = false
    else
      readonly = false
      force = true

      # App Store Connect API Key is required to manage certificates in Apple backend
      # Assume the App Store Connect API .p8 Key is in the ./fastlane/ directory
      app_store_connect_api_key(
        key_id: "XXXXXX",
        issuer_id: "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
        key_filepath: "./fastlane/AuthKey_XXXXXX.p8",
      )
    end

    # Fetch Development certificates for app_identifier
    match(
      type: type,
      app_identifier: app_identifier, 
      readonly: readonly, # Whether to update/upload cert/profile if needed
      force: force # Whether to recreate provisioning profile unconditionally
    )
  end
end

Create Development Certificate & Profile and Upload to Match Repo:

1
bundle exec fastlane match_development type:development isRead:false
  • No errors mean the Development Certificate & Profile were successfully created, installed, and pushed to the Match Repo.

The first time setup requires you to set a Passphrase:

1
2
3
4
[23:29:12]: Enter the passphrase that should be used to encrypt/decrypt your certificates
[23:29:12]: This passphrase is specific per repository and will be stored in your local keychain
[23:29:12]: Make sure to remember the password, as you'll need it when you run match on a different machine
[23:29:12]: Passphrase for Match storage:
  • This value is used as a reference for encrypting all the files in your Match Repo (passphrase + random salt).

  • It is recommended to generate a random string, set it, and record it.

  • Future updates or other users pulling the Match Repo certificates will need to enter this string to decrypt the original files.

Other team members uniformly pull the Development Certificate & Profile from the Match Repo:

1
bundle exec fastlane match_development type:development

The first time you use it, you will be asked for your login password (login keychain) because the certificate needs to be installed into the keychain. Just enter it twice to confirm:

1
2
3
4
5
6
7
8
9
10
[16:52:59]: Installing certificate...
[16:53:00]: There are no local code signing identities found.
You can run security find-identity -v -p codesigning to get this output.
This Stack Overflow thread has more information: https://stackoverflow.com/q/35390072/774.
(Check in Keychain Access for an expired WWDR certificate: https://stackoverflow.com/a/35409835/774 has more info.)
[16:53:00]: Enter the password for /Users/zhgchgli/Library/Keychains/login.keychain-db
[16:53:00]: This passphrase will be stored in your local keychain with the name fastlane_keychain_login and used in future runs
[16:53:00]: This prompt can be avoided by specifying the 'keychain_password' option or 'MATCH_KEYCHAIN_PASSWORD' environment variable
[16:53:00]: Password for login keychain: ********
[16:53:24]: Type password for login keychain again: ********

If Match Development has fetched the certificates, but Xcode still shows errors or invalid status:

You can refer to the common errors mentioned earlier. Most are caused by old, corrupted certificates. Clearing all certificates and then fetching them again should resolve the issue.

Create AdHoc Distribution Certificate & Profile and Upload to Match Repo:

1
bundle exec fastlane match_development type:adhoc isRead:false

Create AppStore Distribution Certificate & Profile and Upload to Match Repo:

1
bundle exec fastlane match_development type:appstore isRead:false

CI/CD services uniformly pull Distribution Certificate & Profile ( isRead:true ) from the Match Repo, then execute the build and release tasks.

Register a New Device

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
platform :ios do  
  desc "Register a new device and refresh profiles"
  lane :registerDevice do \\|options\\|
    # Replace with your App Identifier ID
    app_identifier = "li.zhgchg.myApp"

    # App Store Connect API Key is required to manage certificates in Apple backend
    # Assume the App Store Connect API .p8 Key is in the ./fastlane/ directory
    app_store_connect_api_key(
      key_id: "XXXXXX",
      issuer_id: "XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX",
      key_filepath: "./fastlane/AuthKey_XXXXXX.p8",
    )
    # Input: UDID and device name
    udid = options[:udid] \\|\\| UI.input("Enter device UDID:")
    device_name = options[:name] \\|\\| UI.input("Enter device name:")
    UI.message("📱 Registering device #{device_name} (#{udid})")
    register_device(
      name: device_name,
      udid: udid,
      platform: 'ios'
    )

    # Update Development certificates for app_identifier
    match(
      type: "development",
      app_identifier: app_identifier, 
      readonly: false, # Update and upload cert/profile if needed
      force_for_new_devices: true # Recreate provisioning profile for new devices
    )

    # Update AdHoc certificates for app_identifier
    match(
      type: "adhoc",
      app_identifier: app_identifier, 
      readonly: false, # Update and upload cert/profile if needed
      force_for_new_devices: true # Recreate provisioning profile for new devices
    )
  end
end

After registration, other developers or CI/CD services pulling the Profile (Development or AdHoc) from the Match Repo will include the new devices.

Fastlane Match x CI/CD Workflow Integration

After a general introduction to how Fastlane Match generates Push and Pull certificates, next is to explain how to integrate it into the CI/CD process.

Question 1 — How to Clone Private Match Repo

The most common issue is how to clone the private Match repo in CI/CD. On local development, we use SSH git clone with our own account’s SSH key, so there is no problem. However, CI/CD does not have this key. Although you can use a personal key, it is not secure.

GitHub — Repo Deploy Key:

  1. First, generate the private/public key locally: ssh-keygen -t rsa -b 4096 -f ./id_rsa ( do NOT enter a passphrase )

  2. Go to Match Private Repo -> Settings -> Security -> Deploy keys -> Add deploy key:

  1. Open id_rsa.pub with a text editor, copy the content, and paste it into Key -> “Add key”

  1. Go back to the main Repo -> Settings -> Security -> Secrets and variables

  1. Add SSH Private Key Content to Secret:

  • Name: MATCH_REPO_DEPLOY_PRIVATE_KEY
  1. Back to the main repo’s GitHub Actions setting SSH Key:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
name: CI - Deploy

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout current repo (Repo A)
        uses: actions/checkout@v4

      - name: Setup SSH for Deploy Key
        run: \\|
          mkdir -p ~/.ssh
          echo "${{ secrets.MATCH_REPO_DEPLOY_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan github.com >> ~/.ssh/known_hosts

      - name: Test Clone
        run: \\|
          git clone [email protected]:xxxx/match-certificates.git
# Success!
# .. do deploy job...
  1. Setup Successful!

Question 2 — Secure Storage and Use of App Store Connect API .p8 Key

Because GitHub Actions cannot store files directly, you must first save as a string and then write it to a file.

  1. Same as step 1, add an APP_STORE_CONNECT_API_KEY_CONTENT Secret in the main Repo.

  2. Open AuthKey_XXXXXX.p8 with a text editor, then copy and paste the content.

  3. Add a step in the main repo’s GitHub Actions to read content and write to a file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
name: CI - Deploy
on:
  push:
    branches: [ main ]
  pull_request:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout current repo (Repo A)
        uses: actions/checkout@v4

      - name: Setup SSH for Deploy Key
        run: \\|
          mkdir -p ~/.ssh
          echo "${{ secrets.MATCH_REPO_DEPLOY_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan github.com >> ~/.ssh/known_hosts  
      
      - name: Write Secret Key to File
        env:
          APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
        run: \\|
          # ensure fastlane directory exists
          mkdir -p ./fastlane
      
          # create file path
          APP_STORE_CONNECT_API_KEY_PATH=./fastlane/AuthKey_XXXXXX.p8
      
          # write content to file (keep newline)
          echo "$APP_STORE_CONNECT_API_KEY_CONTENT" > "$APP_STORE_CONNECT_API_KEY_PATH"
      
          # (optional) restrict permissions
          chmod 600 "$APP_STORE_CONNECT_API_KEY_PATH"

      - name: Deploy to Firebase
        env:
          MATCH_PASSWORD: "${{ secrets.MATCH_PASSWORD }}"
        run: bundle exec fastlane deploy_to_firebase

Issue 3 — Setting a Passphrase for the Private Match Repo to Prevent Prompt Interruptions During Match

  1. For the same step as Question 1, add MATCH_PASSWORD to Secrets in the main Repo

  2. Enter the Passphrase for the Fastlane Match Repo you configured

  3. Add env: secret.MATCH_PASSWORD to the main repo’s GitHub Actions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
name: CI - Deploy
on:
  push:
    branches: [ main ]
  pull_request:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout current repo (Repo A)
        uses: actions/checkout@v4

      - name: Setup SSH for Deploy Key
        run: \\|
          mkdir -p ~/.ssh
          echo "${{ secrets.MATCH_REPO_DEPLOY_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan github.com >> ~/.ssh/known_hosts  
      
      - name: Write Secret Key to File
        env:
          APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
        run: \\|
          # ensure fastlane directory exists
          mkdir -p ./fastlane
      
          # create file path
          APP_STORE_CONNECT_API_KEY_PATH=./fastlane/AuthKey_XXXXXX.p8
      
          # write content to file (keep newline)
          echo "$APP_STORE_CONNECT_API_KEY_CONTENT" > "$APP_STORE_CONNECT_API_KEY_PATH"
      
          # (optional) restrict permissions
          chmod 600 "$APP_STORE_CONNECT_API_KEY_PATH"

      - name: Deploy to Firebase
        env:
          MATCH_PASSWORD: "${{ secrets.MATCH_PASSWORD }}"
        run: bundle exec fastlane deploy_to_firebase

Question 4 — Keychain Handling on Self-hosted Runner

Unlike cloud machines that are always clean and new each time, with Self-hosted Runner we can specify derived_data_path, output_directory, buildlog_path, and reinstall_app in Fastlane to ensure a clean environment for each run; however, Certificates and Profiles are installed in the system Keychain app. How should we handle this?

Actually, Fastlane has considered this for us. You can create a clean Keychain before running Match:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
name: CI - Deploy
on:
  push:
    branches: [ main ]
  pull_request:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout current repo (Repo A)
        uses: actions/checkout@v4

      - name: Setup SSH for Deploy Key
        run: \\|
          mkdir -p ~/.ssh
          echo "${{ secrets.MATCH_REPO_DEPLOY_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan github.com >> ~/.ssh/known_hosts  
      
      - name: Write Secret Key to File
        env:
          APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
        run: \\|
          # ensure fastlane directory exists
          mkdir -p ./fastlane
      
          # create file path
          APP_STORE_CONNECT_API_KEY_PATH=./fastlane/AuthKey_XXXXXX.p8
      
          # write content to file (keep newline)
          echo "$APP_STORE_CONNECT_API_KEY_CONTENT" > "$APP_STORE_CONNECT_API_KEY_PATH"
      
          # (optional) restrict permissions
          chmod 600 "$APP_STORE_CONNECT_API_KEY_PATH"

      - name: Create fastlane keychain
        env:
          KEYCHAIN_NAME: "${{ runner.name }}"
          MATCH_PASSWORD: "${{ secrets.MATCH_PASSWORD }}"
        run: \\|
          bundle exec fastlane run create_keychain \
            name:"$KEYCHAIN_NAME" \
            password:"$MATCH_PASSWORD" \
            unlock:true \
            timeout:0 \
            lock_when_sleeps:false
      
      - name: Deploy to Firebase
        env:
          MATCH_PASSWORD: "${{ secrets.MATCH_PASSWORD }}"
          KEYCHAIN_NAME: "${{ runner.name }}"
        run: bundle exec fastlane deploy_to_firebase

      # 🔥 This will run regardless of success or failure above
      - name: Delete fastlane keychain
        if: always()
        env:
          KEYCHAIN_NAME: ${{ runner.name }}
        run: \\|
          bundle exec fastlane run delete_keychain \
            name:"$KEYCHAIN_NAME"
  • Each Runner only executes one job at a time, so we use the Runner Name as the Keychain Name. Each Runner will have its own Keychain.

  • Deleted regardless of success or failure after execution

  • I did not set a separate keychain_password; I used MATCH_PASSWORD uniformly.

Add keychain parameters to the match method in Fastlane/Fastfile:

1
2
3
4
5
6
7
8
9
10
#...
    match(
      type: "adhoc",
      app_identifier: app_identifier, 
      readonly: false, # Update and upload cert/profile if needed
      force_for_new_devices: true, # Recreate provisioning profile for new devices
      keychain_name: ENV['KEYCHAIN_NAME'], # default value: nil
      keychain_password: ENV['MATCH_PASSWORD'] # default value: nil
    )
#...

This way, when Match fetches certificates, it will save them to the specified Keychain instead of the shared login keychain.

Conclusion

The scope of Fastlane is very broad, so here I will only document the process of using Fastlane Match; other lanes like testing, packaging, and publishing will be covered in a separate article if there is a chance. I will also add more Match-related issues and cases as they come up! Feel free to leave questions in the comments!

Further Reading

Please kindly proceed to read. 🤞🏻

If you have any questions or feedback, feel free to contact me.


🍺 Buy me a beer on PayPal

👉👉👉 Follow Me On Medium! (1,053+ Followers) 👈👈👈

This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.

Improve this page on Github.

This post is licensed under CC BY 4.0 by the author.