Enterprise Mac CI: GitLab Runner and GitHub Actions self-hosted runners on one Apple Silicon host

Large Apple Silicon workstations are expensive enough that platform teams ask a blunt question: can GitLab Runner and a GitHub Actions self-hosted runner share one bare-metal Mac without turning releases into a lottery? The answer is yes, if you treat the host as a scheduling problem with a security boundary, not as two installers that happen to fit on disk. This FAQ compares practical knobs — concurrency quotas, label routing, secret isolation, and NVMe cache layout — so you can defend a design note to security and mobile leads. For how fixed DerivedData roots and SwiftPM keys behave under parallel jobs, see 2026 Multi-Repo Parallel Mac Builds: Fixed DerivedData Paths, SwiftPM Cache Keys, Concurrent xcodebuild Disk Peaks & Cleanup — Enterprise Resource Pool FAQ.

1. When co-hosting is rational — and when it is not

Co-hosting pays off when both ecosystems are permanent, you already pay for a high-memory Mac Studio or rack-mounted mini farm, and traffic is bursty but not adversarial. Skip it when compliance demands hard isolation between SCMs, when either side needs exclusive GPU or simulator saturation, or when you cannot get agreement on a single DEVELOPER_DIR policy. If you must mix Command Line Tools-only jobs with full Xcode.app installs, read CLT-only vs full Xcode.app on bare-metal Apple Silicon before you pin two runners to conflicting toolchains.

Rule of thumb: one interactive admin account per physical host is already complex; two CI stacks on the same login session multiply keychain and TCC surprises. Prefer separate macOS users or separate volumes per runner family when auditors ask for blast-radius diagrams.

2. Concurrency quotas: slice CPU, RAM, and job slots explicitly

GitLab Runner exposes concurrent globally plus per-executor limits; GitHub’s runner service honours --max-jobs (or service plist settings) and workflow-level concurrency groups on the server side. Neither product knows about the other’s queue depth, so you must cap simultaneous compile-heavy jobs so two xcodebuild archive runs never fight for the same memory bandwidth. Start with a simple budget: reserve one “heavy” slot for release trains and allow N lightweight linters, then measure P95 tail latency instead of average CPU. Document the matrix in runbooks so on-call engineers do not “temporarily” raise limits during a fire drill.

3. Label routing: keep pickers honest on both platforms

Accidental cross-scheduling is the fastest way to leak secrets across organisations. Use mutually exclusive tags such as gitlab-only versus gha-only at the runner registration layer, then mirror those constraints in workflow runs-on labels and GitLab tags stanzas. Avoid generic labels like macos or apple-silicon on shared hosts unless every job is equally trusted. Pair labels with runner group or repository allow-lists so a forked workflow cannot register a similarly named runner and siphon jobs.

4. Secret isolation: two vendors, one keychain surface

Both runners ultimately execute shell steps as a local user, which means environment variables, files under ~/Library, and the login keychain are shared attack surface unless you partition them. Practical patterns include separate OS users each with its own keychain file, CI-specific signing identities with narrow provisioning profiles, short-lived OIDC tokens instead of long-lived PATs where supported, and never reusing the same RUNNER_TEMP root. Rotate credentials on different cadences per platform so a GitLab token compromise does not automatically equal GitHub repository write access.

5. NVMe cache partitions: compare three layouts before you format anything

Caches collide faster than CPUs. Decide whether GitLab job workspaces, GitHub ACTIONS_RUNNER_DIR, DerivedData, and SwiftPM caches live on one APFS volume with strict per-job subfolders or on separate APFS volumes with quotas. Remote build caches still help, but local NVMe latency wins for incremental compiles.

Layout Pros Risks
Single volume, namespaced pathsSimple backups; easy monitoring of free spaceMisconfigured cleanup can wipe both stacks
Two APFS volumes on same NVMeClear ownership per runner; optional per-volume encryption keysStill shares IOPS; quotas need tuning after Xcode upgrades
Secondary fast SSD via ThunderboltHarder contention; room for hot cache tierCable and power budget; snapshot policy per disk

6. Launch checklist

Walk this list with DevOps and app-security before you merge the infra change.

  • Written concurrency budget per host with links to monitoring dashboards
  • Runner labels verified against every workflow and .gitlab-ci.yml entry
  • Separate secrets stores or keychains per platform, with rotation owners named
  • Disk watermark alerts for each NVMe volume or cache root, not only system volume
  • Game-day test: spike both queues simultaneously and confirm no job steals the wrong cache namespace

Why bare-metal Apple Silicon still wins for mixed runners

Virtualised macOS CI is improving, but two native runner stacks on real Apple Silicon avoid an entire class of nested-virt and USB simulator bugs. Mac mini and Studio-class hardware deliver high memory bandwidth with very low idle power, which matters when you keep warm workers online for both GitLab and GitHub. macOS integrates Xcode, codesigning, and Gatekeeper expectations without a Linux shim, and FileVault plus SIP give security reviewers a familiar story for unattended hosts. If you need additional dedicated cloud Macs to drain one vendor’s queue without touching on-prem capacity, Mac mini M4 remains a pragmatic burst tier: provision cleanly, pin an image, then attach the same runner discipline you use at home.

When you are ready to expand the pool with on-demand dedicated machines, visit the Macstripe home page to compare regions and models, then wire them into the same label and cache policy you documented here.