Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save productdevbook/d2e7bd36082c5f071c98f399bafb8640 to your computer and use it in GitHub Desktop.

Select an option

Save productdevbook/d2e7bd36082c5f071c98f399bafb8640 to your computer and use it in GitHub Desktop.
Isolating a self-hosted macOS GitHub Actions runner from your daily user account

Isolating a self-hosted macOS GitHub Actions runner from your daily user account

If you run a self-hosted GitHub Actions runner on the same Mac you use every day, do not run the runner under your normal desktop user account.

This is especially important for iOS builds that use Fastlane, match, Xcode signing, or any workflow that calls the macOS security CLI.

What can go wrong

A self-hosted runner is not a VM by default. It runs as a normal process on the host, using the home directory, keychain, SSH config, caches, and environment of the user that owns the service.

For iOS builds, Fastlane commonly creates or modifies temporary keychains so it can import certificates and provisioning profiles. If a workflow is cancelled, crashes, or does not restore the keychain state correctly, the desktop user's default keychain or keychain search list can be left pointing at a missing or temporary keychain.

The next time a desktop app tries to store a token or password, macOS may show errors such as:

Keychain Not Found
A keychain cannot be found to store ...

Better setup

Run the self-hosted runner under a dedicated macOS user, for example:

gha-runner

Keep your daily user account separate from CI.

Recommended properties:

  • The runner has its own home directory.
  • The runner has its own login keychain.
  • The runner is started by a LaunchDaemon or service as the dedicated user.
  • CI SSH keys live only under the runner user.
  • Fastlane temporary keychains are created under the runner user.
  • The workflow restores both default-keychain and list-keychains after signing work.

Workflow hardening

For Fastlane-based iOS jobs, add a guard before the build:

set -euo pipefail

LOGIN_KC="$HOME/Library/Keychains/login.keychain-db"

if [ ! -e "$LOGIN_KC" ]; then
  echo "::error::login.keychain-db not found: $LOGIN_KC"
  exit 1
fi

for kc in ~/Library/Keychains/fastlane-ci-*.keychain-db ~/Library/Keychains/fastlane_tmp_keychain*; do
  [ -e "$kc" ] && security delete-keychain "$kc" 2>/dev/null
done

security default-keychain -d user -s "$LOGIN_KC"
security list-keychains -d user -s "$LOGIN_KC"
security default-keychain -d user
security list-keychains -d user

And in cleanup:

LOGIN_KC="$HOME/Library/Keychains/login.keychain-db"

if [ -e "$LOGIN_KC" ]; then
  security default-keychain -d user -s "$LOGIN_KC" 2>/dev/null || true
  security list-keychains -d user -s "$LOGIN_KC" 2>/dev/null || true
fi

security delete-keychain "$MATCH_KEYCHAIN_NAME" 2>/dev/null || true

[ -e "$LOGIN_KC" ] && security default-keychain -d user -s "$LOGIN_KC" 2>/dev/null || true

Key lesson

Self-hosted runners are powerful because they run on your own machine. That also means they can mutate your real machine state.

For macOS signing workflows, treat the runner like infrastructure:

  • isolate it with a dedicated user,
  • keep signing credentials scoped to that user,
  • restore keychain state explicitly,
  • and avoid running CI as your daily desktop account.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment