Docs

Not on GitHub? The same Mac works with every major CI.

We set up GitHub Actions for you and support it first-hand. But your machine is a normal Mac mini with SSH access, and GitLab, Bitbucket, Buildkite, CircleCI, Azure Pipelines and Jenkins all have agents that run on macOS. Install the one you use, and the rest of the machine is identical: pinned Xcode, warm caches, a kept-alive login session for simulators, one customer per box.

Before you start

  • +Every agent above dials out to your CI. Nothing needs an inbound port, so the machine stays off the public internet exactly as it is today.
  • +Run the agent as the runner user, the same account GitHub Actions uses. It owns Xcode, the simulators and the caches.
  • +Keep it alive. Start it as a service (brew services, svc.sh) or inside a tmux session. The runner user is auto-logged-in, so a GUI session exists for simulators and UI tests.
  • +Xcode is installed and pinned. Select a version inside your job with xcode-select or DEVELOPER_DIR, the same as on GitHub.
  • +Remove the GitHub runner if you will not use it, or leave it idle. It costs nothing when no jobs arrive.

First connect over Tailscale and SSH in as runner, exactly as in the setup docs, step 1.

GitLab

GitLab's hosted macOS runners bill at several times the Linux rate. A dedicated Mac with GitLab Runner is the flat-price version of the same thing.

  1. 01

    Install GitLab Runner with Homebrew.

    brew install gitlab-runner
  2. 02

    In your project or group, open CI/CD settings, create a runner, and copy its token. Then register on the Mac.

    gitlab-runner register --url https://gitlab.com --token <runner-token> --executor shell
  3. 03

    Start it as a service for the runner user so it survives logouts and reboots.

    brew services start gitlab-runner
  4. 04

    Tag the runner (for example macos, xcode) and use the tag in your .gitlab-ci.yml jobs.

Bitbucket Pipelines

Atlassian offers no hosted macOS runners at all. Anyone building iOS on Bitbucket must self-host a Mac, which is exactly this.

  1. 01

    The runner needs Java. Install it with Homebrew.

    brew install openjdk@17
  2. 02

    In Bitbucket, open Repository settings (or Workspace settings), then Runners, and add a runner with the macOS type. Bitbucket shows you a start command with your account, repository and runner ids and an OAuth client. Copy it as shown.

  3. 03

    Run that command on the Mac inside a tmux session, or wrap it in a LaunchAgent so it starts at login.

    tmux new -s bitbucket
    # paste the start command Bitbucket gave you
  4. 04

    In bitbucket-pipelines.yml, give the step runs-on: [self.hosted, macos] plus any label you chose.

Buildkite

Buildkite is bring-your-own-compute by design. A dedicated Mac is what a Buildkite iOS pipeline expects.

  1. 01

    Install the agent from Buildkite's Homebrew tap.

    brew install buildkite/buildkite/buildkite-agent
  2. 02

    Put your agent token in the config file, then start it as a service.

    sed -i '' 's/xxx/<your-agent-token>/' "$(brew --prefix)/etc/buildkite-agent/buildkite-agent.cfg"
    brew services start buildkite/buildkite/buildkite-agent
  3. 03

    Add queue or tag rules in the same config file and target them from your pipeline steps.

CircleCI

CircleCI's hosted macOS is metered like GitHub's. Their self-hosted machine runner runs on macOS and connects outbound.

  1. 01

    In CircleCI, create a resource class for self-hosted runners and generate a runner token.

  2. 02

    Install the machine runner from CircleCI's Homebrew tap and follow the configuration steps their UI shows for macOS, which set the token and resource class in the runner config file.

  3. 03

    Start the runner as a service and target the resource class from your config.yml jobs.

CircleCI updates the runner install flow often. Follow the exact commands in their self-hosted runner installation page for macOS.

Azure Pipelines

Microsoft's hosted macOS agents are limited in parallelism. A self-hosted macOS agent is a first-class option.

  1. 01

    In Azure DevOps, open Organization settings, Agent pools, and add an agent. Download the macOS agent package it offers.

  2. 02

    Unpack it in the runner's home and configure it with a personal access token that has Agent Pools read and manage scope.

    mkdir ~/azagent && cd ~/azagent && tar zxvf ~/Downloads/vsts-agent-osx-arm64-*.tar.gz
    ./config.sh
  3. 03

    Install and start it as a service.

    ./svc.sh install
    ./svc.sh start
  4. 04

    Point your pipeline at the pool: pool: { name: <your-pool> }.

Jenkins

Jenkins shops already run their own agents. Use an inbound agent so the Mac dials out to your controller and needs no inbound port.

  1. 01

    Install Java.

    brew install openjdk@17
  2. 02

    In Jenkins, add a new node with the launch method Launch agent by connecting it to the controller. Jenkins shows the agent.jar download and the exact run command with your secret.

  3. 03

    Run that command on the Mac inside tmux, or as a LaunchAgent so it reconnects after a reboot.

    tmux new -s jenkins
    curl -sO https://<your-jenkins>/jnlpJars/agent.jar
    # then the java -jar agent.jar ... command Jenkins gave you
  4. 04

    Label the node macos and restrict the relevant jobs to that label.

Same machine, same price.

Pick your CI system on the signup form. The URL is optional for everything except the fraud check, which is happier with it.