Provisioning Profile
A provisioning profile is an Apple signed file that binds an app identifier, the certificates allowed to sign it, and a device list or a distribution method.
A provisioning profile is a file issued and signed by Apple that binds an application identifier to a set of signing certificates and to either a list of devices or a distribution method. Apple platforms check that binding before they run third party code, so a build that signs cleanly still refuses to install when the profile does not cover the application, the certificate, or the device.
In GitHub Actions the profile is one of two secrets a macOS job needs before it can archive an application for upload. The other is the signing certificate, and each half is checked against the other.
Definition
A provisioning profile is a property list wrapped in a Cryptographic Message Syntax signature. The Apple Developer website creates the profile and signs it, and a device checks that signature before it trusts anything inside (TN3125: Inside Code Signing: Provisioning Profiles, checked on 2026-08-13). Removing the wrapper with security cms -D -i profile.mobileprovision produces the readable XML underneath.
Apple describes a profile as the answer to five questions. Each question maps to a property in that plist:
| Question | Property | What it holds |
|---|---|---|
| Who can sign? | DeveloperCertificates | DER encoded certificates for every identity allowed to sign code covered by the profile |
| What can they sign? | Entitlements > application-identifier | An App ID: a team prefix plus a bundle identifier, which may end in a wildcard |
| Where can it run? | ProvisionedDevices or ProvisionsAllDevices | An explicit list of device identifiers, or a flag covering every device |
| When can it run? | ExpirationDate | The moment the profile stops being accepted |
| How can it be entitled? | Entitlements | An allowlist of entitlements the signed application is permitted to claim |
The entitlements property is an allowlist rather than a description of the application. Every entitlement claimed in the code signature has to appear in the profile, and the profile is free to allow entitlements the application never claims. An App ID that ends in a wildcard, such as TEAMID.com.example.*, covers any bundle identifier with that prefix, which is why one profile can serve an application and its extensions.
The types differ in where and when
The five properties above are the same in every profile. What separates the types is device coverage and how long the profile stays valid.
| Profile type | Device coverage | Typical use |
|---|---|---|
| Development | ProvisionedDevices lists registered devices | Running a debug build on a test device |
| Ad hoc distribution | ProvisionedDevices lists registered devices | Handing a build to testers outside the App Store |
| App Store distribution | No ProvisionedDevices property | Uploading a build for review and release |
| In-house (Enterprise) | ProvisionsAllDevices | Internal distribution inside an organization |
| Developer ID (macOS) | ProvisionsAllDevices | Signing a Mac application distributed outside the App Store |
Validity varies by type and is typically not more than a year, with Developer ID profiles the exception because their expiration date sits far in the future (TN3125, checked on 2026-08-13). A pipeline that signed correctly for months therefore starts failing on an ordinary morning with nothing in the diff to explain it.
Where the file ends up
A profile lives in two places, and the second one is the reason the first one matters. On the build machine it sits in the profile directory of the Xcode being used. Xcode 16 and later read ~/Library/Developer/Xcode/UserData/Provisioning Profiles, and earlier toolchains read ~/Library/MobileDevice/Provisioning Profiles, which is the directory GitHub's own signing example copies into (Installing an Apple certificate on macOS runners, checked on 2026-08-13).
Xcode then embeds the profile in the product it builds. Apple platforms other than macOS expect it at MyApp.app/embedded.mobileprovision, and macOS expects it at MyApp.app/Contents/embedded.provisionprofile. Applications downloaded from the App Store carry no embedded profile, because the store re-signs the application and performs the check itself during distribution.
A certificate and a profile answer different questions. The certificate holds the private key that produces the signature, which is the subject of code signing. The profile is the authorization that says which identities may sign which application, on which devices, and until when.
Example
A distribution build installs the profile before xcodebuild archive runs, because Xcode resolves PROVISIONING_PROFILE_SPECIFIER against the profiles already on disk. This job decodes the profile from a secret, records what it contains, installs it under its UUID, and archives the application for export.
name: release
on:
workflow_dispatch:
jobs:
archive:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Install the distribution provisioning profile
env:
DISTRIBUTION_PROFILE: ${{ secrets.DISTRIBUTION_PROFILE }}
run: |
PROFILES="$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
mkdir -p "$PROFILES"
echo -n "$DISTRIBUTION_PROFILE" | base64 --decode \
> "$RUNNER_TEMP/profile.mobileprovision"
security cms -D \
-i "$RUNNER_TEMP/profile.mobileprovision" \
-o "$RUNNER_TEMP/profile.plist"
UUID="$(/usr/libexec/PlistBuddy -c 'Print :UUID' "$RUNNER_TEMP/profile.plist")"
NAME="$(/usr/libexec/PlistBuddy -c 'Print :Name' "$RUNNER_TEMP/profile.plist")"
EXPIRY="$(/usr/libexec/PlistBuddy -c 'Print :ExpirationDate' "$RUNNER_TEMP/profile.plist")"
APP_ID="$(/usr/libexec/PlistBuddy -c 'Print :Entitlements:application-identifier' \
"$RUNNER_TEMP/profile.plist")"
echo "profile $NAME covers $APP_ID and expires $EXPIRY"
cp "$RUNNER_TEMP/profile.mobileprovision" "$PROFILES/$UUID.mobileprovision"
echo "PROFILE_NAME=$NAME" >> "$GITHUB_ENV"
- name: Archive
run: |
xcodebuild archive \
-scheme App \
-configuration Release \
-destination "generic/platform=iOS" \
-archivePath "$RUNNER_TEMP/App.xcarchive" \
CODE_SIGN_STYLE=Manual \
DEVELOPMENT_TEAM=${{ vars.DEVELOPMENT_TEAM }} \
PROVISIONING_PROFILE_SPECIFIER="$PROFILE_NAME"
- name: Export for upload
run: |
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/App.xcarchive" \
-exportOptionsPlist ExportOptions.plist \
-exportPath "$RUNNER_TEMP/export"The job above installs the profile only. Importing the certificate into a temporary keychain is a separate step, and code signing iOS builds on GitHub Actions covers that half along with the keychain commands that keep codesign from asking for an interactive authorization.
Three details in the install step earn their lines. Naming the installed file after the profile's UUID is the usual convention and stops two profiles for the same application from overwriting each other. Printing the name, the App ID, and the expiration puts the profile's identity in the run log, so an expiry failure a year later is one log line away from an explanation instead of a bisect. Reading the profile through security cms -D and PlistBuddy means the job inspects the file it is about to install rather than the file someone believes is in the secret.
ExportOptions.plist repeats part of the same information. Its provisioningProfiles dictionary maps each bundle identifier in the product to a profile name or UUID, and its method key states the distribution method. A mismatch between the profile installed above and the entry in that dictionary surfaces at export rather than at archive, which is why the export step is worth reading closely when the archive succeeded.
Two failures cover most of the profile related output. No profiles for 'com.example.App' were found means no installed profile carries a matching App ID, usually because the secret was never decoded into the directory the selected Xcode reads. Provisioning profile "X" doesn't include signing certificate "Y" means the profile and the certificate came from different rotations, so the profile's DeveloperCertificates array and the identity in the keychain no longer agree.
Related Terms
- Code signing iOS builds on GitHub Actions: the keychain, certificate, and profile steps of a signing job in one workflow file.
- Code signing: the signature a profile authorizes, and how the operating system checks it.
- Uploading a signed build to TestFlight from GitHub Actions: what happens to the exported archive after the profile has done its work.
- Cloud runners documentation: the runner labels available for macOS jobs, with sizes and images.
- Preinstalled software documentation: the tooling each macOS runner image carries, including the Xcode versions on disk.
- Runner pricing: per minute rates by runner type.
FAQ
What is a provisioning profile?
A provisioning profile is a property list wrapped in a Cryptographic Message Syntax signature and signed by Apple. It binds an application identifier to the certificates allowed to sign that application, to a device list or a distribution method, and to an expiration date, and it carries the allowlist of entitlements the signed code may claim.
What is the difference between a signing certificate and a provisioning profile?
A certificate holds the private key that produces a signature and establishes who signed the code. A profile is the authorization that says which identities may sign which application, on which devices, and until when. A build can fail on either half on its own, and the error message usually names only one of them.
Where does a GitHub Actions job install a provisioning profile?
Into the profile directory of the Xcode the job selects. Xcode 16 and later read ~/Library/Developer/Xcode/UserData/Provisioning Profiles, and earlier toolchains read ~/Library/MobileDevice/Provisioning Profiles. The profile is stored base64 encoded in a repository or environment secret and decoded into that directory before xcodebuild archive runs.
Start with $10 in free credits
Change the runner label in your workflow and keep the rest of your GitHub Actions setup. Runner time is billed per minute.