Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Set Up Buildkite Agents on a Hosted Mac for iOS CI

For an ARM Buildkite agent that builds and signs iOS apps, request a Figment M-series Mac starting at $95/month — the exact chip generation, RAM, and disk are confirmed in the quote, and physical plans are request-based rather than instant. If you still need an Intel toolchain (for example, a project pinned to Xcode 26 or earlier on x86_64), the $29/month Figment virtual Intel macOS VM (2 vCPU, 8 GiB RAM, 80 GiB disk; access via the browser console, no GPU or audio) can host an Intel Buildkite agent. It is a real Intel macOS environment that runs compatible toolchains directly. Start a request at /mac-hosting/start.

The two primary sources you will keep open while reading this are the Buildkite macOS install docs and the Buildkite pipeline step reference.

Buildkite agent vs GitHub-hosted runner: when a dedicated Mac wins

The two models solve different problems:

FactorGitHub-hosted macOS runnerSelf-hosted Buildkite agent on a Mac
Billing modelPer-minute, metered against your Actions allotmentFlat monthly rate for the host
Xcode versionsWhatever GitHub's image shipsAny version you install
Caches between jobsDiscardedPreserved on disk
Signing identityRe-imported each jobInstalled once in a persistent keychain
ConcurrencyShared pool, queuedAs many executors as the Mac can sustain

If your team runs more than a handful of iOS builds per day, flat-rate hardware generally beats metered minutes once you factor in warm caches and signing setup. The crossover depends on your per-minute rate and build length — work the math with your own numbers before choosing.

Installing the Buildkite agent on macOS via Homebrew

Open a terminal session on the Mac. On the Figment Intel VM, use the browser console's desktop terminal; on a physical Mac host, use whichever access path your quote sets up. Then:

# Install the agent from the official Buildkite tap
brew install buildkite/buildkite/buildkite-agent

# Write your agent token into the installed config
sed -i '' "s/xxx/INSERT-YOUR-AGENT-TOKEN-HERE/g" \
  "$(brew --prefix)/etc/buildkite-agent/buildkite-agent.cfg"

Grab the token from your Buildkite organization under Agents → New Agent. Config paths differ by architecture (see the install docs):

ArchitectureConfig path
Apple silicon/opt/homebrew/etc/buildkite-agent/buildkite-agent.cfg
Intel/usr/local/etc/buildkite-agent/buildkite-agent.cfg

Smoke-test the connection:

buildkite-agent start
# Look for: "Starting buildkite-agent" followed by "successfully connected"

Stop it with Ctrl-C and move on to tags.

Configuring agent tags for Xcode and platform routing

Tags are how Buildkite picks an agent. A step that declares agents: { xcode: "16" } will only run on an agent that advertises xcode=16. All keys on the step must match.

A working buildkite-agent.cfg for an M-series Mac:

# buildkite-agent.cfg — Apple silicon CI node
token="INSERT-YOUR-AGENT-TOKEN-HERE"

# Agent display name; %n differentiates spawned siblings
name="mac-ci-%n"

# Routing tags consumed by pipeline.yml
tags="xcode=16,os=macos,arch=arm64,queue=ios-builds"

# Build workspace (Apple silicon default)
build-path="/opt/homebrew/buildkite-agent/builds"

# Hooks directory (pre-command, post-command, etc.)
hooks-path="/opt/homebrew/etc/buildkite-agent/hooks"

# Run two concurrent agents from one config
spawn=2

Pick the Xcode value that matches what you actually install. Intel hosts can run Xcode 26 or earlier (see Apple's porting notes for Apple silicon); Xcode 27 requires Apple silicon. visionOS simulators also require Apple silicon. For anything newer than Xcode 26, choose the M-series host.

If you are migrating from an older agent config, note that cancel-grace-period from agent v3 is replaced in v4 by --cancel-signal-timeout and --cancel-cleanup-timeout; check the v3-to-v4 upgrade guide before copying older snippets.

Restart the service to pick up the changes:

brew services restart buildkite/buildkite/buildkite-agent

Both spawned agents should appear in the Buildkite UI under Agents.

Routing iOS steps with pipeline.yml

Put the following at .buildkite/pipeline.yml in your repository. Replace the command paths and the scheme name with values that exist in your project — do not copy an invented scheme.

# .buildkite/pipeline.yml
steps:
  - label: ":hammer_and_wrench: Build iOS"
    key: build-ios
    command: ".buildkite/scripts/build-ios.sh"
    agents:
      xcode: "16"
      os: macos
      arch: arm64
    timeout_in_minutes: 30
    artifact_paths: "build/**/*.ipa"

  - label: ":test_tube: Unit tests"
    key: test-ios
    command: ".buildkite/scripts/test-ios.sh"
    agents:
      xcode: "16"
      arch: arm64
    timeout_in_minutes: 20

  - label: ":rocket: Upload to TestFlight"
    key: upload-testflight
    command: "bundle exec fastlane beta"
    depends_on: build-ios
    agents:
      xcode: "16"
      queue: ios-builds
    timeout_in_minutes: 15

Inside build-ios.sh, invoke xcodebuild with flags that actually exist — -project or -workspace, -scheme, -configuration, -destination, and -derivedDataPath. Let the script exit on failure (set -euo pipefail) so Buildkite marks the job red instead of recording a fast-but-broken run.

Hooks for keychain unlock and workspace cleanup

Agent hooks live in hooks-path. Store the unlock password in Buildkite's pipeline Environment Variables and reference it by name; never commit it to the hook file, and do not export the agent's full environment to disk — real secrets leak that way.

hooks/pre-command — unlock the signing keychain only when a step needs it:

#!/usr/bin/env bash
set -euo pipefail

if [[ "${BUILDKITE_LABEL:-}" == *"Build iOS"* || \
      "${BUILDKITE_LABEL:-}" == *"TestFlight"* ]]; then
  security unlock-keychain -p "$KEYCHAIN_PASSWORD" \
    "$HOME/Library/Keychains/CI.keychain-db"
  echo "--- keychain unlocked"
fi

Provisioning profiles are plain files, not keychain items — install them to ~/Library/MobileDevice/Provisioning Profiles/ separately. Signing identities (certificate + private key) belong in an encrypted .p12 that you import into the CI keychain during machine bootstrap, not on every build.

hooks/post-command — keep disk use predictable:

#!/usr/bin/env bash
set -euo pipefail

rm -rf "$HOME/Library/Developer/Xcode/DerivedData/ModuleCache.noindex"
echo "--- DerivedData module cache cleared"

Mark both executable: chmod +x hooks/pre-command hooks/post-command.

If a build has to reach your own artifact store, binary cache, or a corporate TestFlight upload proxy, update the allowlist on that specific service. Do not add rules for invented App Store Connect IP ranges — Apple does not publish a static one for upload traffic.

Running multiple agents on one Mac

spawn=2 launches two agent processes from one config; each takes one executor slot. How many an individual Mac can sustain depends on the exact chip, core layout, and RAM in your quote — we do not publish a benchmark. A conservative starting point is one executor per 8 GiB of installed RAM, then measure and adjust. While a parallel build is running:

top -l 5 -s 2 -o cpu | head -30

If wall-clock time jumps materially, drop spawn or ask for a larger configuration.

A short cost worksheet: Figment vs two Mac hosts, entry prices only

Treat this as an entry-price comparison. Chip, RAM, and disk differ across these plans, and physical Figment plans collect requests until supply is confirmed, so verify specifics in the actual quote.

Provider / planHeadline rate12-month spendWhat's listed on the vendor page
Figment M-series (entry)$95/mo$1,140M-series Mac; exact chip/RAM/disk confirmed in quote (mac-hosting)
Figment Managed Build Mac$249/mo$2,988One M-series Mac plus CI setup, backups, monitoring, 30 min/mo support
CloudMac monthly$99.99/mo$1,199.88Mac mini M4, 16 GB, 256 GB (256 GB "Reserve now"; 512 GB/1 TB "Fully reserved")
CloudMac yearly$999/yr$999Same M4 base config, prepaid annual
MacStadium M4.S$149/mo$1,788M4, 16 GB, 256 GB; support and 99.99% uptime claim on the pricing page

A few honest observations from those numbers:

  • CloudMac's prepaid year at $999 is cheaper than twelve months of Figment's $95 entry rate ($1,140) by $141 and ships with a specified 16 GB M4 today, where Figment's $95 entry does not name a chip until you quote.
  • Figment's $29 Intel VM is the lowest entry price in the set, but it is Intel-on-KVM. Pick it only when your toolchain supports x86_64; otherwise choose M-series.
  • Figment's differentiator at the Managed Build Mac tier is the specific managed CI work (agent setup, backups, monitoring, included support minutes), not a hardware price advantage over CloudMac's self-serve Mac. Compare on what you want the vendor to run, not only on the sticker.

Ship it

  • Install the agent, land the config, and register against Buildkite.
  • Tag by Xcode, OS, and arch; route iOS steps with a matching agents: block.
  • Keep signing material in a dedicated keychain, unlock it only when needed, and clean DerivedData between jobs.
  • For managed CI with your Buildkite agents preinstalled and monitored, request the Managed Build Mac at /mac-hosting/start.

Related guides


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms