The point of running the CLI in CI is to stop committing signing material to a private repository and stop fragile bash glue around the Apple Developer portal. Instead, store the certificate and profile IDs as pipeline variables, fetch them by ID with the CLI, import them, and run `xcodebuild` as normal.
What you need
- A service credential with `hexsign-api/read` scope (write isn't needed if you only fetch).
- The IDs of the certificate and profile you want to sign with — the dashboard exposes the ID on each detail page; the CLI prints it via `hexsign certificates list -o json`.
- An ephemeral, isolated keychain per run so the imported certificate doesn't leak into other jobs.
The CI step
# .github/workflows/release.yml
- name: Fetch signing material
env:
HEXSIGN_CLIENT_ID: ${{ secrets.HEXSIGN_CLIENT_ID }}
HEXSIGN_CLIENT_SECRET: ${{ secrets.HEXSIGN_CLIENT_SECRET }}
PROFILE_ID: ${{ vars.HEXSIGN_PROFILE_ID }}
CERT_ID: ${{ vars.HEXSIGN_CERT_ID }}
run: |
hexsign certificates download "$CERT_ID" --output-dir build/sign
hexsign profiles download "$PROFILE_ID" --output-dir build/sign`certificates download` writes a `.p12` and a sibling `.password` file (both `0600`). `profiles download` writes the `.mobileprovision` Apple expects. Both files land in the directory you pick — keep it inside the workspace so the run-specific filesystem cleanup wipes it at the end.
Import and sign
# Create an ephemeral keychain KEYCHAIN="build.keychain" security create-keychain -p "$RUNNER_KEYCHAIN_PW" "$KEYCHAIN" security set-keychain-settings -lut 21600 "$KEYCHAIN" security unlock-keychain -p "$RUNNER_KEYCHAIN_PW" "$KEYCHAIN" security list-keychains -d user -s "$KEYCHAIN" login.keychain # Import the certificate P12_PASSWORD="$(cat build/sign/*.password)" security import build/sign/*.p12 \ -k "$KEYCHAIN" -P "$P12_PASSWORD" -T /usr/bin/codesign # Install the profile where Xcode looks for it mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles cp build/sign/*.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/ # Then xcodebuild as usual xcrun xcodebuild archive \ -workspace MyApp.xcworkspace \ -scheme MyApp \ -archivePath build/MyApp.xcarchive
After the build
Most CI runners give every job a fresh, isolated VM, so the keychain is destroyed when the job ends. If you're on long-lived runners, delete the keychain explicitly at the end of the step so the next job doesn't inherit it.
security delete-keychain build.keychain rm -rf build/sign
Why not commit the .p12 to the repo?
- Anyone with read access to the repo gets the private key forever.
- Rotating the certificate means rotating the file in every branch and fork that mirrors the repo.
- An incident response — "who had access to the cert and when" — relies on git history, not on a managed audit log.
- Fetching by ID with the CLI gives you a per-call audit entry and instant revocation.