Architecting CI/CD Pipelines on macOS via GitLab Runner

The implementation of Continuous Integration and Continuous Deployment (CI/CD) within the Apple ecosystem presents unique architectural challenges that differ significantly from standard Linux-based containerized environments. When integrating GitLab Runner into a macOS environment, engineers are not merely installing a binary; they are navigating the complexities of macOS security models, user session management, and hardware-specific optimizations for both Intel x86-64 and Apple Silicon architectures. This technical deep dive explores the deployment, configuration, and troubleshooting of GitLab Runner on macOS, ensuring a robust foundation for mobile and desktop application development.

The requirement for macOS runners stems from the necessity to compile and sign code for the Apple ecosystem, including iOS, macOS, watchOS, and tvOS. Because these platforms require specific Xcode toolchains and proprietary signing certificates, the execution environment must have access to the user's keychain and UI session. This necessity dictates the fundamental way the GitLab Runner service must be architected on this operating system.

Architectural Paradigms of macOS GitLab Runner Services

In the macOS ecosystem, service management is handled by launchd. However, GitLab Runner does not operate as a system-level daemon. Instead, it utilizes a specific service mode that is critical for successful job execution.

The distinction between a LaunchDaemon and a LaunchAgent is the most vital concept for any DevOps engineer setting up a macOS runner. A LaunchDaemon runs at the system level, starts at boot, and executes with root privileges. Cru-cially, a LaunchDaemon has no access to a user's active graphical session or their personal keychain. Because iOS simulators and code-signing processes require access to the user's keychain and the ability to interact with the UI session, a LaunchDaemon is fundamentally incapable of performing the tasks required for Apple-centric CI/CD.

Consequently, GitLab Runner on macOS runs exclusively as a user-mode LaunchAgent. This specific implementation has several direct implications for the runner's behavior and the host machine's configuration:

  • The runner operates as the currently authenticated user rather than the root user.
  • The service lifecycle is tied to the user session; it starts when the user signs in and terminates when the user signs out.
  • It maintains access to the user’s keychain and UI session, which is mandatory for running the iOS Simulator and performing code signing.
  • Configuration is stored within the user's home directory at ~/.gitlab-runner/config.toml.

To mitigate the risk of the runner becoming unavailable after a system reboot, administrators must enable automatic login on the macOS machine. This ensures that upon a power cycle or automatic update, the user session is established without manual intervention, thereby triggering the LaunchAgent to start the GitLab Runner service.

Feature LaunchAgent (Supported) LaunchDaemon (Unsupported)
Execution Level User-mode System-level
Privileges Authenticated User Root
Keychain Access Full Access None
UI Session Access Yes No
Lifecycle Trigger User Login System Boot
Configuration Path ~/.gitlab-runner/config.toml N/A

Installation Methodologies and Hardware Compatibility

GitLab Runner is designed to be cross-platform, supporting various architectures including Apple Silicon (arm64) and Intel x86-64. The installation process varies depending on whether the user prefers manual binary management or the Homebrew package manager.

Homebrew Installation via Formulae

For many developers, Homebrew provides the most streamlined method for managing the gitlab-runner lifecycle. The gitlab-runner formula is a well-maintained package that supports the latest macOS versions.

macOS Version Apple Silicon (tahoe) Intel
Sequoia Supported Supported
Sonoma Supported Supported
Earlier Versions Supported Supported

The Homebrew version allows for easy updates and dependency management. It is important to note that if building from source, the go programming language (specifically version 1.26.3 or later) is a required dependency to compile the software.

Manual Installation and Prerequisites

When installing manually, the environment must meet specific prerequisites to ensure stability. The most critical requirement is that the operator must be signed in to the macOS machine as the specific user account intended to run the CI/CD jobs. Performing installation steps via an SSH session is explicitly discouraged, as it may fail to correctly map the user's environment variables and session permissions.

The installation can be categorized by the tier of GitLab service being utilized:

  • GitLab.com (SaaS)
  • GitLab Self-Managed
  • GitLab Dedicated

Hosted macOS Runners on GitLab.com

For organizations that do not wish to maintain their own physical Mac hardware, GitLab offers hosted macOS runners as a Beta service. These runners provide on-demand macOS environments that are fully integrated into the GitLab CI/CD pipeline.

Hosted runners are available for Premium and Ultimate tiers. Unlike Linux runners, which utilize Docker containers, macOS runners use Virtual Machine (VM) images to provide the necessary environment.

Machine Specifications for Hosted Runners

Users can select from different machine types based on the computational intensity of their build jobs.

Runner Tag vCPUs Memory Storage
saas-macos-medium-m1 4 8 GB 50 GB
saas-macos-large-m2pro 6 16 GB 50 GB

When working with hosted runners, developers must specify the desired macOS image in their .gitlab-ci.yml file. While Linux environments allow for arbitrary Docker images, macOS environments are constrained to the specific VM images provided by GitLab.

For projects requiring an Intel x86-64 environment on Apple Silicon-based runners, developers can utilize Rosetta 2 to emulate the necessary architecture, ensuring compatibility with legacy build tools.

Advanced Virtualization with Tart and GitLab Runner

For high-performance requirements, specifically when a need exists to run multiple macOS virtual machines in parallel on a single host, the gitlab-tart-executor architecture is employed. While the original project has been superseded by the official Cirrus Labs implementation (gitlab-tart-executor), the underlying concept remains highly relevant for advanced DevOps workflows.

This method leverages the macOS Virtualization Framework to provision VMs for jobs using the tart command-line tool. This allows for a highly scalable, ephemeral environment where each CI job can run in its own isolated macOS instance.

Implementation Requirements for Tart-based Runners

Setting up a runner that utilizes the macOS virtualization framework requires a specific set of dependencies and configurations:

  • Install the required binaries using Homebrew:
    bash brew install gitlab-runner daemonize cirruslabs/cli/tart
  • Ensure the host system possesses an SSH private key for VM communication; if one does not exist, generate it using:
    bash ssh-keygen -t ed25519
  • Adjust the execution paths within the configuration file. The paths for prepare_exec, run_exec, and cleanup_exec must be manually updated within the gitlab-runner-example-config.toml to match the local environment.

In this architecture, the job image is selected via the image: tag in the .gitlab-ci.yml file. For example, a user might pull an image such as ghcr.io/cirruslabs/macos-monterey-xcode:14.

Configuration and Pipeline Integration

Once the runner is installed and registered, the final step involves configuring the .gitlab-ci.yml file to direct jobs to the new macOS runner. This is achieved through the use of tags.

Registration Process

When registering the runner, the user must select shell as the executor. Upon successful registration, a confirmation message will appear: Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!.

To verify the runner's status within the GitLab interface:
1. Navigate to the project or group via the top navigation bar.
2. Select Settings > CI/CD.
3. Expand the Runners section to view the active runner and its associated tags.

Sample CI/CD Configuration for macOS

The following configuration demonstrates a typical workflow for a mobile application, including dependency installation via CocoaPods and building via Fastlane. It is imperative that the tags used in the YAML file match the tags assigned during the registration of the macOS runner.

```yaml
stages:
- build
- test

variables:
LANG: "en_US.UTF-8"

before_script:
- gem install bundler
- bundle install
- gem install cocoapods
- pod install

build:
stage: build
script:
- bundle exec fastlane build
tags:
- macos

test:
stage: test
script:
- bundle exec fastlane test
tags:
- macos
```

Troubleshooting Common macOS Deployment Failures

Deploying GitLab Runner on macOS is frequently met with specific errors related to the operating system's security and service management layers.

Resolving "Error: killed: 9" on Apple Silicon

Users on Apple Silicon hardware may encounter the Error: killed: 9 message when attempting to execute commands such as:
- gitlab-runner install
- gitlab-runner start
- gitlab-runner register

This error typically stems from permission or path issues within the LaunchAgent configuration. To resolve this, the directories defined for StandardOutPath and StandardErrorPath in the ~/Library/LaunchAgents/gitlab-runner.plist file must exist and possess writable permissions.

Resolving "FATAL: Failed to start gitlab-runner: exit status 134"

A common failure mode involves the runner failing to start as a service, often presenting as exit status 134 without generating any log files. This issue is prevalent on Darwin arm64 systems. The resolution requires a complete clean reinstall of the service components.

The following sequence of commands should be executed to resolve this state:

  1. Uninstall the existing service:
    bash gitlab-runner uninstall
  2. Manually remove the service plist file from the user's library:
    bash rm ~/Library/LaunchAgents/gitlab-runner.plist
  3. Remove the existing configuration file:
    bash rm ~/.gitlab-runner/config.toml
  4. Reinstall the service specifically assigned to the target user (replace {your_username} with the actual username):
    bash sudo gitlab-runner install --user {your_username}
  5. Re-register the runner:
    bash sudo gitlab-runner register

Analysis of Deployment Strategies

The deployment of GitLab Runner on macOS is not a "set and forget" operation but a carefully managed service integration. The requirement for the runner to act as a LaunchAgent rather than a LaunchDaemon is the defining characteristic of its architecture. This design choice prioritates access to the user's secure enclave (keychain) and graphical environment, which are non-negotiable for modern Apple platform development.

Engineers must weigh the benefits of self-managed hardware against the convenience of GitLab's hosted runners. Self-managed runners offer maximum control over the hardware and local toolchains but demand significant overhead in maintaining "always-on" user sessions and managing physical macOS machines. Hosted runners alleviate this burden but introduce constraints regarding image availability and machine specifications.

Furthermore, the emergence of virtualization-based runners (such as those utilizing Tart) represents the next evolution in macOS CI/CD. By treating macOS as a highly portable, ephemeral virtual machine rather than a static physical host, organizations can achieve the same level of scalability and isolation that has long been the standard for Linux-based CI/CD pipelines. Success in this domain requires a deep understanding of the interplay between launchd, user session permissions, and the specific requirements of the Xcode build ecosystem.

Sources

  1. GitLab Documentation: Install GitLab Runner on macOS
  2. Homebrew Formulae: gitlab-runner
  3. GitLab Documentation: Hosted Runners on macOS
  4. GitLab Forum: gitlab-runner error on macOS install
  5. GitLab Documentation: macOS Setup
  6. GitHub: Conntac/gitlab-runner-tart

Related Posts