Skip to content
Latchkey

Code Signing for iOS in GitHub Actions

Sign iOS builds in CI by loading a certificate and profile into a throwaway keychain.

A real iOS release has to be signed, which means CI needs your distribution certificate and provisioning profile available on the runner. The safe pattern is to base64-encode both, store them as secrets, and import them into a temporary keychain that exists only for the run. This recipe shows that setup.

What the pipeline does

  • Decodes a base64 certificate and provisioning profile from secrets.
  • Creates a temporary keychain and imports the signing identity.
  • Installs the provisioning profile where Xcode expects it.
  • Runs a signed xcodebuild archive.

The workflow

Store the .p12 certificate, its password, and the provisioning profile as base64 secrets, then materialize them at runtime.

.github/workflows/ios-signed.yml
name: iOS Signed Build
on:
  push:
    branches: [main]
jobs:
  archive:
    runs-on: macos-14
    steps:
      - uses: actions/checkout@v4
      - name: Import signing assets
        env:
          CERT_BASE64: \${{ secrets.IOS_CERT_BASE64 }}
          CERT_PASSWORD: \${{ secrets.IOS_CERT_PASSWORD }}
          PROFILE_BASE64: \${{ secrets.IOS_PROFILE_BASE64 }}
          KEYCHAIN_PASSWORD: \${{ secrets.KEYCHAIN_PASSWORD }}
        run: |
          echo "$CERT_BASE64" | base64 --decode > cert.p12
          echo "$PROFILE_BASE64" | base64 --decode > profile.mobileprovision
          security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
          security set-keychain-settings -lut 21600 build.keychain
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
          security import cert.p12 -k build.keychain -P "$CERT_PASSWORD" -T /usr/bin/codesign
          security list-keychains -d user -s build.keychain
          security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" build.keychain
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
      - name: Archive
        run: |
          xcodebuild archive \
            -scheme MyApp \
            -archivePath build/MyApp.xcarchive \
            -configuration Release

Notes for this platform

Signing requires a macos runner, so isolate this in a release-only job to limit costly macOS minutes. Set a keychain timeout (set-keychain-settings -lut) so the unlocked keychain self-locks. The set-key-partition-list step is the one people miss -- without it codesign prompts for access and the build hangs. fastlane match is a higher-level alternative that manages certs in a git repo. Managed runners auto-retry transient keychain and network failures so a single flaky import does not waste a whole macOS run.

Key takeaways

  • Import the cert and profile into a temporary keychain that lives only for the run.
  • Run set-key-partition-list or codesign will hang waiting for keychain access.
  • Keep signing in a release-only macOS job to limit expensive minutes.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →