Signing works locally but fails on CI with a missing-profile error
A CI job archives the app and fails at the signing step. The same scheme signs and runs fine on every developer's Mac, and nothing about the bundle id or the source changed.
Constraints:
- The runner is a clean, headless machine — no one is logged into Xcode on it.
- You must not weaken security (no disabling code signing, no committing private keys).
The build log shows:
error: No profiles for 'com.acme.app' were found: Xcode couldn't find any
iOS Distribution provisioning profiles matching 'com.acme.app'.
Automatic signing is disabled and unable to generate a profile.
Diagnose the cause.
CI is a clean machine — no login keychain — so it lacks the certificate and profile your Mac accumulated, and automatic signing needs an Apple ID session CI lacks. Fix by manual signing: install them into a keychain on the runner via fastlane match.
- ✗Assuming CI has the same keychain and profiles as a developer's Mac
- ✗Relying on automatic signing on a headless runner with no Apple ID session
- ✗Disabling code signing on CI instead of provisioning certificates securely
- →Why does automatic signing need an Apple ID session a headless runner lacks?
- →How does a tool like fastlane match keep the certificate off the runner's permanent keychain?
The failure is environmental, not in the code. A developer's Mac has a login keychain that has quietly accumulated the signing certificate and the downloaded provisioning profile; automatic signing also leans on a live Apple ID session in Xcode. A clean CI runner has neither, so it cannot find (or auto-generate) a profile for com.acme.app.
The fix is explicit, manual signing driven by secrets the runner fetches at build time:
# Create a throwaway keychain and import the distribution certificate (.p12)
security create-keychain -p "$KC_PASS" build.keychain
security import cert.p12 -k build.keychain -P "$P12_PASS" -T /usr/bin/codesign
security list-keychains -s build.keychain
# Install the matching provisioning profile where Xcode looks for it
cp acme_distribution.mobileprovision \
~/Library/MobileDevice/Provisioning\ Profiles/
# Build with manual signing (no Apple ID session required)
xcodebuild -scheme App -archivePath App.xcarchive archive \
CODE_SIGN_STYLE=Manual \
PROVISIONING_PROFILE_SPECIFIER="Acme Distribution"
In practice fastlane match automates exactly this — it stores the certificate and profile encrypted in a private repo and installs them into a temporary keychain per run, so nothing sensitive lives permanently on the runner. Never "fix" it by setting CODE_SIGNING_REQUIRED=NO.