Figment.so
Mac hostingWebsite hostingGuidesHow to usePricing

Self-Hosted Mac Runner for GitHub Actions

Where a self-hosted Mac runner pays off

GitHub-hosted macOS runners bill at $0.062 per minute on the standard 3- or 4-core macOS image (About billing for GitHub Actions). A 20-minute iOS build run 100 times a month is $124; 200 runs is $248. Queues on the shared macOS pool add wall-clock time on top of that bill, and every run starts from a cold toolchain without your Pods or DerivedData.

Self-hosted runners are free to use with Actions; you pay the fixed cost of the host instead (About self-hosted runners). A Figment M-series Mac starts at $95/month — the entry price for an M-series configuration; the exact chip, RAM, and disk are set in your quote, not advertised as a fixed spec. Against $0.062/minute, $95 covers 1,532 build-minutes per month (77 twenty-minute builds, or 153 ten-minute builds). Past that, the flat rate wins.

For teams that would rather not run the runner themselves, Figment's Managed Build Mac at $249/month adds one M-series Mac, CI setup, backups, monitoring, and 30 minutes of monthly support. The Managed Mac Team at $999/month covers two M-series Macs, two CI runners, team setup, backups, monitoring, and one hour of monthly support.

One hardware caveat before you choose a plan: the $29 virtual Intel macOS VM (2 vCPU, 8 GiB RAM, 80 GiB disk) can run compatible Intel Mac toolchains, including supported Xcode 26 or earlier on Intel (Porting your macOS apps to Apple silicon). It has no GPU acceleration, no audio, and cannot execute ARM-only binaries or visionOS simulators. For ARM builds, SwiftUI previews that need a real GPU, or visionOS, pick an M-series plan.

Prerequisites

RequirementDetail
Figment M-series MacFrom $95/month — request a configuration quote
GitHub permissionsAdmin on the target repository (or org for org-level runners)
Runner registration tokenSettings → Actions → Runners → New self-hosted runner (tokens expire within the hour)
Shell access to the MacAccess method is set in your Figment quote; the commands below assume a terminal session on the host
XcodeInstalled at a known versioned path, e.g. /Applications/Xcode_16.2.app
Homebrew + CocoaPodsPre-installed so CI jobs do not trigger interactive installs

Copy the exact download URL and token from the "New self-hosted runner" screen — the versioned URL changes with each runner release, so don't hard-code it.

Install and register the runner

Open a terminal on your Figment Mac and run these commands, substituting the values GitHub shows you:

mkdir -p ~/actions-runner && cd ~/actions-runner

# GitHub's dialog shows the exact versioned URL — paste it here
curl -o actions-runner-osx-arm64.tar.gz -L \
  "https://github.com/actions/runner/releases/download/vX.Y.Z/actions-runner-osx-arm64-X.Y.Z.tar.gz"
tar xzf ./actions-runner-osx-arm64.tar.gz

# Configure with your org/repo URL and the token from the dialog
./config.sh \
  --url "https://github.com/YOUR_ORG/YOUR_REPO" \
  --token "YOUR_REGISTRATION_TOKEN" \
  --name "figment-mac-01" \
  --labels "self-hosted,macOS,figment-m-series,ios-ci" \
  --work "_work" \
  --unattended

# Install as a persistent launchd service and start it
./svc.sh install
./svc.sh start

The runner should appear in Settings → Actions → Runners with an Idle status shortly after. The --labels flag is what lets workflows target this exact machine. Keep self-hosted and macOS as the standard pair, then add project-specific labels (figment-m-series, ios-ci) so you can route job types to specific hosts later.

The annotated ios.yml workflow

Save this to .github/workflows/ios.yml and replace the capitalized placeholders with your own project's workspace and scheme. It targets the labeled runner, pins Xcode, caches Pods and DerivedData, and exits non-zero on build failure — this file is an illustrative example, not a benchmarked run.

name: iOS CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-and-test:
    # Only lands on your registered Figment Mac, never GitHub-hosted
    runs-on: [self-hosted, macOS, figment-m-series]

    env:
      XCODE_VERSION: "16.2"            # Pin — bump deliberately, not silently
      DERIVED_DATA: ~/Library/Developer/Xcode/DerivedData/CI

    steps:
      - uses: actions/checkout@v4

      - name: Select Xcode ${{ env.XCODE_VERSION }}
        run: sudo xcode-select -s /Applications/Xcode_${{ env.XCODE_VERSION }}.app

      - name: Restore CocoaPods cache
        id: pods-cache
        uses: actions/cache@v4
        with:
          path: |
            Pods
            ~/.cocoapods/repos
          key: pods-${{ runner.os }}-${{ hashFiles('**/Podfile.lock') }}
          restore-keys: |
            pods-${{ runner.os }}-

      - name: Install pods
        if: steps.pods-cache.outputs.cache-hit != 'true'
        run: pod install --repo-update

      - name: Restore DerivedData cache
        uses: actions/cache@v4
        with:
          path: ${{ env.DERIVED_DATA }}
          key: derived-${{ runner.os }}-${{ hashFiles('**/*.xcodeproj/project.pbxproj') }}
          restore-keys: |
            derived-${{ runner.os }}-

      - name: Build and test
        run: |
          set -euo pipefail
          xcodebuild test \
            -workspace YOUR_WORKSPACE.xcworkspace \
            -scheme YOUR_SCHEME \
            -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
            -derivedDataPath "$DERIVED_DATA" \
            -resultBundlePath TestResults.xcresult \
            | xcpretty

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results-${{ github.run_id }}
          path: TestResults.xcresult

set -euo pipefail on the build step matters: when piping to xcpretty, bash runs both processes concurrently, and without pipefail the step exits zero even when xcodebuild fails. A failed build must never be counted as a successful fast run.

Caching and secrets — what belongs where

On a persistent Figment Mac the working directory survives between jobs, so DerivedData that didn't make it into the Actions cache still lives on disk until something clears it. Use the cache action for portability and to seed fresh workspaces, but don't put credentials in it.

PathCache?Why
Pods/YesSkips full re-install when Podfile.lock hasn't changed
~/.cocoapods/reposYesAvoids re-cloning the specs repo
DerivedDataYesReuses compiled intermediates
Signing certificates / .p12NoInstall from an encrypted CI secret into a temporary keychain each run, then delete
Provisioning profilesNoDeliver as files through the CI secret store; they are not exported from Keychain
.env filesNoNever export the shell environment to a plaintext file — it can contain keys. Transfer required secret names and restore values from your CI secret manager

Update allowlists only on infrastructure you actually control — your own artifact store, package proxy, or CI service. Don't assume fixed IP ranges for third-party developer services.

Harden the runner

GitHub's guidance treats self-hosted runners as unsuitable for public repositories because anyone who can open a pull request can run code on your host (Security hardening for self-hosted runners). The practical controls:

  • Private repos only. If you must use self-hosted on a public repo, use ephemeral just-in-time runners that handle one job and self-remove.
  • Dedicated non-admin user. Create a macOS account specifically for the runner. Keep its login keychain free of sensitive keys; secrets arrive per-job and leave with the job.
  • Rotate signing certificates and API tokens on a schedule. Don't wait for expiry.
  • Concurrency limits. concurrency: group: ${{ github.ref }} cancels in-flight runs on new pushes, shortening the exposure window on long jobs.

Does the math work for you?

GitHub-hosted macOS bills by the minute with no monthly cap; a Figment M-series runner is flat. At $95/month vs $0.062/minute the crossover is 1,532 build-minutes per month.

Monthly build volumeGitHub-hosted macOSFigment M-series (from $95/mo)
500 min (25 × 20 min)$31.00$95
1,000 min (50 × 20 min)$62.00$95
~1,532 min — break-even~$95$95
2,500 min (125 × 20 min)$155.00$95
5,000 min (250 × 20 min)$310.00$95

This compares monthly cost at a given build volume, not identical hardware. Below ~1,532 build-minutes you save money staying hosted and avoid any runner maintenance. Above it, a Figment Mac is cheaper and your queue is yours. If you'd rather hand off the runner itself, Managed Build Mac ($249) or Managed Mac Team ($999) covers setup, backups, monitoring, and a defined block of support time each month.

Start a Figment M-series Mac configured for CI →


Related guides:


Subscribe for Figma design and website publishing guides.


Figment.so

Contact

Twitter

Website pricing

Compute index

About

Privacy

Terms