{"meta":{"title":"Installing an Apple certificate on macOS runners for Xcode development","intro":"Learn how to sign Xcode apps within a continuous integration (CI) workflow by installing an Apple code signing certificate on GitHub Actions runners.","product":"GitHub Actions","breadcrumbs":[{"href":"/en/actions","title":"GitHub Actions"},{"href":"/en/actions/how-tos","title":"How-tos"},{"href":"/en/actions/how-tos/deploy","title":"Deploy"},{"href":"/en/actions/how-tos/deploy/deploy-to-third-party-platforms","title":"Deploy to third-party platforms"},{"href":"/en/actions/how-tos/deploy/deploy-to-third-party-platforms/sign-xcode-applications","title":"Sign Xcode applications"}],"documentType":"article"},"body":"# Installing an Apple certificate on macOS runners for Xcode development\n\nLearn how to sign Xcode apps within a continuous integration (CI) workflow by installing an Apple code signing certificate on GitHub Actions runners.\n\n## Prerequisites\n\nYou should be familiar with YAML and the syntax for GitHub Actions. For more information, see:\n\n* [Writing workflows](/en/actions/how-tos/write-workflows)\n* [Workflow syntax for GitHub Actions](/en/actions/reference/workflows-and-actions/workflow-syntax)\n\nYou should have an understanding of Xcode app building and signing. For more information, see the [Apple developer documentation](https://developer.apple.com/documentation/).\n\n## Creating secrets for your certificate and provisioning profile\n\nThe signing process involves storing certificates and provisioning profiles, transferring them to the runner, importing them to the runner's keychain, and using them in your build.\n\nTo use your certificate and provisioning profile on a runner, we strongly recommend that you use GitHub secrets. For more information on creating secrets and using them in a workflow, see [Using secrets in GitHub Actions](/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets).\n\nCreate secrets in your repository or organization for the following items:\n\n* Your Apple signing certificate.\n\n  * This is your `p12` certificate file. For more information on exporting your signing certificate from Xcode, see the [Xcode documentation](https://help.apple.com/xcode/mac/current/#/dev154b28f09).\n\n  * You should convert your certificate to Base64 when saving it as a secret. In this example, the secret is named `BUILD_CERTIFICATE_BASE64`.\n\n  * Use the following command to convert your certificate to Base64 and copy it to your clipboard:\n\n    ```shell\n    base64 -i BUILD_CERTIFICATE.p12 | pbcopy\n    ```\n\n* The password for your Apple signing certificate.\n  * In this example, the secret is named `P12_PASSWORD`.\n\n* Your Apple provisioning profile.\n\n  * For more information on exporting your provisioning profile from Xcode, see the [Xcode documentation](https://help.apple.com/xcode/mac/current/#/deva899b4fe5).\n\n  * You should convert your provisioning profile to Base64 when saving it as a secret. In this example, the secret is named `BUILD_PROVISION_PROFILE_BASE64`.\n\n  * Use the following command to convert your provisioning profile to Base64 and copy it to your clipboard:\n\n    ```shell\n    base64 -i PROVISIONING_PROFILE.mobileprovision | pbcopy\n    ```\n\n* A keychain password.\n\n  * A new keychain will be created on the runner, so the password for the new keychain can be any new random string. In this example, the secret is named `KEYCHAIN_PASSWORD`.\n\n## Add a step to your workflow\n\nThis example workflow includes a step that imports the Apple certificate and provisioning profile from the GitHub secrets, and installs them on the runner.\n\n```yaml copy\nname: App build\non: push\n\njobs:\n  build_with_signing:\n    runs-on: macos-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v6\n      - name: Install the Apple certificate and provisioning profile\n        env:\n          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}\n          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}\n          BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}\n          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}\n        run: |\n          # create variables\n          CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12\n          PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision\n          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db\n\n          # import certificate and provisioning profile from secrets\n          echo -n \"$BUILD_CERTIFICATE_BASE64\" | base64 --decode -o $CERTIFICATE_PATH\n          echo -n \"$BUILD_PROVISION_PROFILE_BASE64\" | base64 --decode -o $PP_PATH\n\n          # create temporary keychain\n          security create-keychain -p \"$KEYCHAIN_PASSWORD\" $KEYCHAIN_PATH\n          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH\n          security unlock-keychain -p \"$KEYCHAIN_PASSWORD\" $KEYCHAIN_PATH\n\n          # import certificate to keychain\n          security import $CERTIFICATE_PATH -P \"$P12_PASSWORD\" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH\n          security set-key-partition-list -S apple-tool:,apple: -k \"$KEYCHAIN_PASSWORD\" $KEYCHAIN_PATH\n          security list-keychain -d user -s $KEYCHAIN_PATH\n\n          # apply provisioning profile\n          mkdir -p ~/Library/MobileDevice/Provisioning\\ Profiles\n          cp $PP_PATH ~/Library/MobileDevice/Provisioning\\ Profiles\n      - name: Build app\n          # ...\n```\n\n> \\[!NOTE]\n> For iOS build targets, your provisioning profile should have the extension `.mobileprovision`. For macOS build targets, the extension should be `.provisionprofile`. The example workflow above should be updated to reflect your target platform.\n\n## Required clean-up on self-hosted runners\n\nGitHub-hosted runners are isolated virtual machines that are automatically destroyed at the end of the job execution. This means that the certificates and provisioning profile used on the runner during the job will be destroyed with the runner when the job is completed.\n\nOn self-hosted runners, the `$RUNNER_TEMP` directory is cleaned up at the end of the job execution, but the keychain and provisioning profile might still exist on the runner.\n\nIf you use self-hosted runners, you should add a final step to your workflow to help ensure that these sensitive files are deleted at the end of the job. The workflow step shown below is an example of how to do this.\n\n```yaml\n- name: Clean up keychain and provisioning profile\n  if: ${{ always() }}\n  run: |\n    security delete-keychain $RUNNER_TEMP/app-signing.keychain-db\n    rm ~/Library/MobileDevice/Provisioning\\ Profiles/build_pp.mobileprovision\n```"}