Guide - CI/CD

How to Set Up a CircleCI Self-Hosted Runner on Mac Mini M4

Complete step-by-step guide to installing and configuring a CircleCI self-hosted machine runner on a dedicated Mac Mini M4. Native Apple Silicon performance, persistent caching, and up to 80% cheaper than CircleCI cloud macOS resources.

35 min read Updated February 2025

1. Why Use a Self-Hosted CircleCI Runner on Mac?

CircleCI offers cloud-based macOS execution environments, but they come with significant costs and limitations. macOS resources on CircleCI use a credit-based system where each minute on a macOS executor consumes 50-100 credits depending on the resource class. For teams running frequent iOS builds, this can quickly add up to $300-$500 per month or more.

A self-hosted runner on a dedicated Mac Mini M4 from MyRemoteMac eliminates per-minute billing entirely. You get a dedicated Apple Silicon machine with persistent storage, warm caches, and full root access for a flat monthly fee starting at $75/month. Here is a detailed comparison:

Feature CircleCI Cloud macOS MyRemoteMac Self-Hosted
Cost Model 50-100 credits/min (~$0.06-$0.12/min) $75/mo flat (unlimited minutes)
Architecture Intel x86 or M1 (shared) Apple M4 (dedicated, latest)
Build Speed ~14 min (medium iOS project) ~5 min (same project)
Cache Persistence Ephemeral (must restore each job) Persistent on disk (instant)
Queue Wait Time 30s - 5min (varies by plan) 0s (dedicated hardware)
Privacy / Data Control Shared infrastructure Dedicated machine, full control
Custom Software Pre-installed image only Full root access, any software

Key Benefit: With a persistent self-hosted runner, DerivedData, Swift Package Manager caches, and CocoaPods are preserved between builds. This alone can cut build times by 50-70% compared to CircleCI's ephemeral cloud macOS environments that start from scratch every time. Combined with the M4 chip's superior single-threaded performance, your iOS builds will be dramatically faster.

2. Prerequisites

Before you begin, make sure you have the following:

  • A Mac Mini M4 server from MyRemoteMac (from $75/mo)
  • A CircleCI account with a Performance, Scale, or Server plan (self-hosted runners require a paid plan)
  • SSH access to your Mac Mini (provided with your MyRemoteMac subscription)
  • An Apple Developer account (for code signing and provisioning profiles)
  • Basic familiarity with YAML, CircleCI configuration, and terminal commands

3. Step 1: SSH into Your Mac Mini and Install Dependencies

First, connect to your Mac Mini M4 via SSH. You will have received your credentials when you set up your MyRemoteMac server. We need to install Xcode and the tools required for iOS builds before setting up the CircleCI runner.

Connect via SSH

# Connect to your Mac Mini M4
ssh admin@your-server-ip

# Verify you're on Apple Silicon
uname -m
# Expected output: arm64

# Check macOS version
sw_vers
# ProductName:    macOS
# ProductVersion: 15.2
# BuildVersion:   24C101

Install Homebrew and Xcode Command Line Tools

# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Add Homebrew to PATH
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"

# Install Xcode Command Line Tools
xcode-select --install

# Accept the license agreement
sudo xcodebuild -license accept

Install Xcode (Full Version)

For iOS builds, you need the full Xcode application. The fastest way to install it on a headless server is using xcodes:

# Install xcodes CLI tool
brew install xcodes

# List available Xcode versions
xcodes list

# Install the latest stable Xcode
xcodes install 16.2

# Set it as the active Xcode
sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer

# Verify installation
xcodebuild -version
# Xcode 16.2
# Build version 16C5032a

Install iOS Simulators and Build Tools

# Install the iOS 18 simulator runtime
xcodebuild -downloadPlatform iOS

# Verify simulator availability
xcrun simctl list runtimes
# == Runtimes ==
# iOS 18.2 (18.2 - 22C150) - com.apple.CoreSimulator.SimRuntime.iOS-18-2

# Install additional build tools
brew install xcbeautify fastlane swiftlint

4. Step 2: Install the CircleCI Runner Agent

CircleCI uses a machine runner agent to connect your Mac Mini to the CircleCI platform. The machine runner receives jobs from CircleCI and executes them directly on your macOS host. This is different from the container runner (which only works on Linux).

Create a Dedicated User for the Runner

It is recommended to create a dedicated user to run the CircleCI agent for security isolation:

# Create a circleci user (optional but recommended)
sudo dscl . -create /Users/circleci
sudo dscl . -create /Users/circleci UserShell /bin/zsh
sudo dscl . -create /Users/circleci RealName "CircleCI Runner"
sudo dscl . -create /Users/circleci UniqueID 550
sudo dscl . -create /Users/circleci PrimaryGroupID 20
sudo dscl . -create /Users/circleci NFSHomeDirectory /Users/circleci
sudo mkdir -p /Users/circleci
sudo chown circleci:staff /Users/circleci

# Or simply use your existing admin user (simpler setup)

Download and Install the Runner Agent

# Create a directory for the runner
sudo mkdir -p /opt/circleci

# Download the latest CircleCI machine runner for macOS ARM64
# Check https://circleci.com/docs/runner-installation-mac/ for latest version
curl -o /tmp/circleci-runner.pkg \
  https://circleci-binary-releases.s3.amazonaws.com/circleci-runner/1.0/circleci-runner_darwin_arm64.pkg

# Install the runner package
sudo installer -pkg /tmp/circleci-runner.pkg -target /

# Verify the installation
circleci-runner --version

Configure the Runner Agent

Create the runner configuration file. You will need your runner authentication token from the CircleCI dashboard (covered in Step 3). For now, create the config file structure:

# Create the configuration directory
sudo mkdir -p /opt/circleci/config

# Create the runner configuration file
sudo tee /opt/circleci/config/runner-agent-config.yaml << 'EOF'
api:
  auth_token: YOUR_RUNNER_TOKEN_HERE

runner:
  name: mac-mini-m4-runner
  working_directory: /opt/circleci/workdir
  cleanup_working_directory: true
  max_run_time: 5h

  # Optional: limit concurrent tasks
  # command_prefix: ["nice", "-n", "10"]

logging:
  file: /opt/circleci/logs/runner.log
EOF

# Create required directories
sudo mkdir -p /opt/circleci/workdir
sudo mkdir -p /opt/circleci/logs

# Set permissions (adjust user if using dedicated circleci user)
sudo chown -R admin:staff /opt/circleci

Create a macOS Launch Agent for Persistence

To ensure the runner starts automatically on boot and restarts if it crashes, create a macOS LaunchDaemon:

# Create the LaunchDaemon plist
sudo tee /Library/LaunchDaemons/com.circleci.runner.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.circleci.runner</string>
    <key>ProgramArguments</key>
    <array>
        <string>/opt/circleci/circleci-runner</string>
        <string>machine</string>
        <string>--config</string>
        <string>/opt/circleci/config/runner-agent-config.yaml</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
    <key>StandardOutPath</key>
    <string>/opt/circleci/logs/runner-stdout.log</string>
    <key>StandardErrorPath</key>
    <string>/opt/circleci/logs/runner-stderr.log</string>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    </dict>
</dict>
</plist>
EOF

# Load and start the service
sudo launchctl load /Library/LaunchDaemons/com.circleci.runner.plist

# Verify the service is running
sudo launchctl list | grep circleci
# Expected: PID listed with com.circleci.runner

# Check the logs
tail -f /opt/circleci/logs/runner.log

Important: The runner agent will not connect until you add a valid authentication token. Complete Step 3 first to generate the token, then update the runner-agent-config.yaml file with the real token and restart the service.

5. Step 3: Configure the Runner in CircleCI Dashboard

Now you need to register your runner in the CircleCI web interface and generate an authentication token. This connects your Mac Mini to your CircleCI organization.

Create a Resource Class

In CircleCI, self-hosted runners are organized by resource classes. A resource class is a label that maps your .circleci/config.yml to a specific set of runners.

  1. Go to CircleCI DashboardOrganization SettingsSelf-Hosted Runners
  2. Click "Create Resource Class"
  3. Set the Namespace to your organization name (e.g., your-org)
  4. Set the Resource Class name to something descriptive (e.g., mac-runner)
  5. This creates a resource class identifier: your-org/mac-runner

Generate a Runner Authentication Token

  1. After creating the resource class, click "Create New Token"
  2. Give the token a descriptive name (e.g., mac-mini-m4-token)
  3. Copy the generated token immediately — it will only be shown once

Update the Runner Configuration with Your Token

# SSH back into your Mac Mini
ssh admin@your-server-ip

# Update the runner configuration with your real token
sudo nano /opt/circleci/config/runner-agent-config.yaml

# Replace YOUR_RUNNER_TOKEN_HERE with the actual token:
# api:
#   auth_token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

# Restart the runner service to apply the new token
sudo launchctl unload /Library/LaunchDaemons/com.circleci.runner.plist
sudo launchctl load /Library/LaunchDaemons/com.circleci.runner.plist

# Verify the runner connects successfully
tail -20 /opt/circleci/logs/runner.log
# Look for: "Runner is ready to receive jobs"

Once connected, the runner should appear as "Online" in the CircleCI dashboard under Self-Hosted Runners. If it does not appear within 60 seconds, check the log file for error messages.

Using the CircleCI CLI (Alternative)

You can also manage runners via the CircleCI CLI:

# Install the CircleCI CLI
brew install circleci

# Authenticate with CircleCI
circleci setup

# Create a resource class via CLI
circleci runner resource-class create your-org/mac-runner \
  "Mac Mini M4 Runner" --generate-token

# List your runners
circleci runner instance list your-org/mac-runner

6. Step 4: Create Your .circleci/config.yml for iOS Builds

Create a .circleci/config.yml file in the root of your repository. The key difference from a standard CircleCI config is using machine: true with your custom resource_class to target your self-hosted runner.

version: 2.1

jobs:
  build-and-test:
    machine: true
    resource_class: your-org/mac-runner

    environment:
      SCHEME: "MyApp"
      DESTINATION: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2"
      DERIVED_DATA_PATH: "DerivedData"
      SPM_CACHE_PATH: ".spm-cache"

    steps:
      - checkout

      - run:
          name: Select Xcode version
          command: |
            sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
            xcodebuild -version

      - run:
          name: Resolve Swift Package Dependencies
          command: |
            xcodebuild -resolvePackageDependencies \
              -scheme "$SCHEME" \
              -clonedSourcePackagesDirPath "$SPM_CACHE_PATH"

      - run:
          name: Build the app
          command: |
            xcodebuild build \
              -scheme "$SCHEME" \
              -destination "$DESTINATION" \
              -clonedSourcePackagesDirPath "$SPM_CACHE_PATH" \
              -derivedDataPath "$DERIVED_DATA_PATH" \
              | xcbeautify

      - run:
          name: Run unit tests
          command: |
            xcodebuild test \
              -scheme "$SCHEME" \
              -destination "$DESTINATION" \
              -clonedSourcePackagesDirPath "$SPM_CACHE_PATH" \
              -derivedDataPath "$DERIVED_DATA_PATH" \
              -resultBundlePath TestResults.xcresult \
              | xcbeautify

      - store_test_results:
          path: TestResults.xcresult

      - store_artifacts:
          path: TestResults.xcresult
          destination: test-results

workflows:
  ios-pipeline:
    jobs:
      - build-and-test:
          filters:
            branches:
              only:
                - main
                - develop

Note: The resource_class: your-org/mac-runner value must exactly match the resource class you created in the CircleCI dashboard. If it does not match, jobs will remain queued indefinitely.

7. Step 5: Optimize with Caching and Parallelism

One of the biggest advantages of a self-hosted runner is persistent caching. Because the runner's filesystem is persistent, you do not need to upload and download caches like you do with CircleCI cloud resources. However, there are additional optimizations to maximize your build speed.

Leverage Persistent DerivedData

Since the runner disk persists between builds, DerivedData is already cached automatically. Use a consistent path:

# In your config.yml steps, always use a consistent DerivedData path:
- run:
    name: Build with persistent cache
    command: |
      xcodebuild build \
        -scheme "$SCHEME" \
        -destination "$DESTINATION" \
        -derivedDataPath ~/DerivedData/"$SCHEME" \
        -clonedSourcePackagesDirPath ~/spm-cache \
        | xcbeautify

# Periodically clean old DerivedData on the runner (cron job):
# 0 3 * * 0 find ~/DerivedData -maxdepth 1 -mtime +7 -exec rm -rf {} +

Workspace Persistence Between Jobs

Use CircleCI workspaces to pass artifacts between jobs in the same workflow:

jobs:
  build:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Build
          command: |
            xcodebuild build \
              -scheme "MyApp" \
              -destination "generic/platform=iOS" \
              -derivedDataPath DerivedData
      - persist_to_workspace:
          root: .
          paths:
            - DerivedData

  test:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - attach_workspace:
          at: .
      - run:
          name: Run tests
          command: |
            xcodebuild test \
              -scheme "MyApp" \
              -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
              -derivedDataPath DerivedData \
              | xcbeautify

workflows:
  build-test:
    jobs:
      - build
      - test:
          requires:
            - build

Parallel Test Execution

- run:
    name: Run tests in parallel
    command: |
      xcodebuild test \
        -scheme "MyApp" \
        -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
        -destination "platform=iOS Simulator,name=iPhone 15,OS=17.5" \
        -parallel-testing-enabled YES \
        -maximum-parallel-testing-workers 4 \
        -derivedDataPath DerivedData \
        | xcbeautify

CircleCI Test Splitting

For large test suites, use CircleCI's built-in test splitting to distribute tests across parallel runners:

jobs:
  test:
    machine: true
    resource_class: your-org/mac-runner
    parallelism: 3
    steps:
      - checkout
      - run:
          name: Split and run tests
          command: |
            # Generate test plan
            TESTS=$(circleci tests glob "**/*Tests.swift" | \
              circleci tests split --split-by=timings)

            # Run only this container's portion of tests
            xcodebuild test \
              -scheme "MyApp" \
              -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
              -only-testing:$TESTS \
              | xcbeautify

8. Workflow Examples

Here are complete, production-ready workflow examples for common iOS CI/CD scenarios.

Full iOS Build, Test, and Deploy Pipeline

version: 2.1

jobs:
  lint:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Run SwiftLint
          command: swiftlint lint --reporter json > swiftlint-results.json || true
      - store_artifacts:
          path: swiftlint-results.json

  build-and-test:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Select Xcode
          command: sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
      - run:
          name: Resolve dependencies
          command: |
            xcodebuild -resolvePackageDependencies \
              -scheme "MyApp" \
              -clonedSourcePackagesDirPath ~/spm-cache
      - run:
          name: Build and test
          command: |
            xcodebuild test \
              -scheme "MyApp" \
              -destination "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2" \
              -clonedSourcePackagesDirPath ~/spm-cache \
              -derivedDataPath ~/DerivedData/MyApp \
              -resultBundlePath TestResults.xcresult \
              -parallel-testing-enabled YES \
              | xcbeautify
      - store_test_results:
          path: TestResults.xcresult
      - store_artifacts:
          path: TestResults.xcresult

  deploy-testflight:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Select Xcode
          command: sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
      - run:
          name: Install certificates with Fastlane Match
          command: |
            fastlane match appstore --readonly
      - run:
          name: Build and upload to TestFlight
          command: |
            fastlane beta
          environment:
            FASTLANE_USER: ${APPLE_ID}
            MATCH_PASSWORD: ${MATCH_PASSWORD}

workflows:
  ios-pipeline:
    jobs:
      - lint
      - build-and-test:
          requires:
            - lint
      - deploy-testflight:
          requires:
            - build-and-test
          filters:
            branches:
              only: main

Fastlane Integration Example

If you use Fastlane for your build automation, here is how to integrate it with your CircleCI self-hosted runner:

# Fastfile (fastlane/Fastfile)
default_platform(:ios)

platform :ios do
  desc "Run tests"
  lane :test do
    scan(
      scheme: "MyApp",
      device: "iPhone 16 Pro",
      derived_data_path: "~/DerivedData/MyApp",
      result_bundle: true,
      output_directory: "./test_output"
    )
  end

  desc "Build and push to TestFlight"
  lane :beta do
    match(type: "appstore", readonly: true)
    increment_build_number(
      build_number: ENV["CIRCLE_BUILD_NUM"]
    )
    gym(
      scheme: "MyApp",
      export_method: "app-store",
      derived_data_path: "~/DerivedData/MyApp"
    )
    pilot(skip_waiting_for_build_processing: true)
  end
end
# .circleci/config.yml using Fastlane
version: 2.1

jobs:
  fastlane-test:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: bundle install
      - run:
          name: Run Fastlane tests
          command: bundle exec fastlane test
      - store_test_results:
          path: ./test_output
      - store_artifacts:
          path: ./test_output

  fastlane-deploy:
    machine: true
    resource_class: your-org/mac-runner
    steps:
      - checkout
      - run:
          name: Install dependencies
          command: bundle install
      - run:
          name: Deploy to TestFlight
          command: bundle exec fastlane beta

workflows:
  ios-workflow:
    jobs:
      - fastlane-test
      - fastlane-deploy:
          requires:
            - fastlane-test
          filters:
            branches:
              only: main

9. Performance Comparison

Here is a detailed performance and cost comparison between CircleCI cloud macOS resources and a self-hosted Mac Mini M4 from MyRemoteMac:

Build Time Benchmarks

Scenario CircleCI Cloud macOS Self-Hosted Mac Mini M4 Improvement
Clean build (medium app) 14 min 5 min 2.8x faster
Incremental build 14 min (no cache) 1.5 min 9.3x faster
SPM dependency resolution 3-5 min (download each time) 5 sec (cached on disk) ~60x faster
Unit test suite (500 tests) 8 min 2.5 min 3.2x faster
Queue wait time 30s - 5 min 0 sec Instant

Monthly Cost Analysis

Team Size Builds/Month CircleCI Cloud Cost MyRemoteMac Cost Monthly Savings
Solo Dev 100 builds (10 min avg) $100/mo $75/mo $25/mo
Small Team (5) 500 builds (10 min avg) $500/mo $75/mo $425/mo
Medium Team (15) 1500 builds (10 min avg) $1,500/mo $179/mo (M4 Pro) $1,321/mo
Enterprise (50+) 5000+ builds $5,000+/mo $358/mo (2x M4 Pro) $4,642+/mo

Bottom Line: For any team running more than ~75 builds per month, a self-hosted Mac Mini M4 pays for itself immediately. The cost savings compound further because builds are faster on dedicated hardware with warm caches, meaning each build consumes fewer minutes in the first place.

10. Troubleshooting Common Issues

Runner disconnects or shows as "Offline"

The runner agent may lose connection due to network issues or a crashed process. The LaunchDaemon should auto-restart, but if it does not:

# Check if the process is running
ps aux | grep circleci-runner

# Check the LaunchDaemon status
sudo launchctl list | grep circleci

# View the error logs
tail -50 /opt/circleci/logs/runner-stderr.log
tail -50 /opt/circleci/logs/runner.log

# Restart the service
sudo launchctl unload /Library/LaunchDaemons/com.circleci.runner.plist
sudo launchctl load /Library/LaunchDaemons/com.circleci.runner.plist

# If the token expired, generate a new one in CircleCI dashboard
# and update /opt/circleci/config/runner-agent-config.yaml

resource_class not found or jobs stuck in queue

If jobs stay queued with "No matching runner found", the resource class in your config does not match the dashboard:

# Verify the exact resource class name in CircleCI dashboard:
# Organization Settings > Self-Hosted Runners > Resource Classes

# The resource_class in .circleci/config.yml must match exactly:
# resource_class: your-org/mac-runner  (case-sensitive!)

# Check your runner is online:
circleci runner instance list your-org/mac-runner

# Common mistakes:
# - Wrong namespace (org name vs personal namespace)
# - Typo in resource class name
# - Runner is offline or token is invalid
# - Using 'docker' executor instead of 'machine: true'

Xcode code signing errors

Code signing on a CI machine requires careful keychain management. The runner process may not have access to the login keychain:

# Option 1: Use Fastlane Match (recommended)
# In your Fastfile:
match(type: "appstore", readonly: true)

# Option 2: Manual keychain management in your job steps
- run:
    name: Setup code signing
    command: |
      # Decode and import the certificate
      echo "$BUILD_CERTIFICATE_BASE64" | base64 --decode > /tmp/cert.p12

      # Create a temporary keychain
      security create-keychain -p "ci" /tmp/ci.keychain
      security set-keychain-settings -lut 21600 /tmp/ci.keychain
      security unlock-keychain -p "ci" /tmp/ci.keychain

      # Import the certificate
      security import /tmp/cert.p12 -P "$P12_PASSWORD" \
        -A -t cert -f pkcs12 -k /tmp/ci.keychain
      security list-keychain -d user -s /tmp/ci.keychain

      # Install provisioning profile
      mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
      echo "$PROVISIONING_PROFILE_BASE64" | base64 --decode \
        > ~/Library/MobileDevice/Provisioning\ Profiles/profile.mobileprovision

Simulator fails to boot or times out

Simulators can get into a bad state on long-running CI machines. Reset them between builds:

# Add a pre-build step to clean simulators
- run:
    name: Reset simulators
    command: |
      xcrun simctl shutdown all 2>/dev/null || true
      xcrun simctl erase all 2>/dev/null || true

# If a specific runtime is missing, reinstall it:
xcodebuild -downloadPlatform iOS

# List available simulators
xcrun simctl list devices available

Disk space running low

Xcode builds generate significant amounts of data. Set up automatic cleanup on your runner:

# Check available disk space
df -h /

# Clean old DerivedData
rm -rf ~/Library/Developer/Xcode/DerivedData/*

# Remove old simulator runtimes
xcrun simctl runtime delete all

# Clean Homebrew cache
brew cleanup --prune=7

# Remove old CircleCI working directories
find /opt/circleci/workdir -maxdepth 1 -mtime +3 -exec rm -rf {} +

# Set up a weekly cron job for automatic cleanup
(crontab -l 2>/dev/null; echo "0 4 * * 0 rm -rf ~/Library/Developer/Xcode/DerivedData/* && brew cleanup --prune=7") | crontab -

11. Frequently Asked Questions

How much does a CircleCI self-hosted runner on Mac Mini M4 cost?

A dedicated Mac Mini M4 from MyRemoteMac starts at $75/month with unlimited build minutes. This is significantly cheaper than CircleCI's cloud macOS runners which cost approximately $0.06-$0.12 per minute (roughly $300-500/month for active teams). Self-hosted runners have no per-minute charges, so your costs are predictable regardless of build volume.

Can I use a CircleCI self-hosted runner for iOS and macOS builds?

Yes. A self-hosted runner on a Mac Mini M4 can run any macOS workload including iOS builds, macOS app builds, Swift package testing, Xcode UI tests, and Fastlane automation. Since it runs natively on Apple Silicon, builds are faster than on emulated or Intel-based cloud runners.

What is the difference between CircleCI machine runner and container runner?

CircleCI machine runner executes jobs directly on the host machine's operating system, which is required for macOS/iOS builds that need Xcode, simulators, and Apple frameworks. Container runner executes jobs inside Docker containers and is only available on Linux. For Mac builds, you must use the machine runner.

How do I keep my CircleCI self-hosted runner updated?

The CircleCI machine runner agent supports automatic updates by default. You can also manually update by downloading the latest release from CircleCI and replacing the binary. It is recommended to check for updates monthly and keep Xcode and macOS updated as well.

Is a self-hosted runner faster than CircleCI cloud macOS?

Yes, typically 2-3x faster for clean builds and up to 9x faster for incremental builds. CircleCI cloud macOS runners use shared Intel or M1 hardware with ephemeral environments, meaning every build starts with a cold cache. A self-hosted Mac Mini M4 has dedicated Apple Silicon performance, persistent DerivedData and SPM caches, and no queue wait times.

Can I run multiple CircleCI jobs concurrently on one Mac Mini?

Yes. The Mac Mini M4 has a 10-core CPU and 16GB or more of RAM, which can comfortably handle 2-3 concurrent build jobs. For heavier workloads, the Mac Mini M4 Pro with 14 cores and 24GB RAM can handle 4-6 concurrent jobs. You can configure the runner's maximum concurrent tasks in the runner configuration file.

Start with a Mac Mini M4 for Your CircleCI Pipelines

Deploy a dedicated Mac Mini M4 as your CircleCI self-hosted runner. Unlimited build minutes starting at $75/month with a 7-day free trial.