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.
- Go to CircleCI Dashboard → Organization Settings → Self-Hosted Runners
- Click "Create Resource Class"
- Set the Namespace to your organization name (e.g.,
your-org) - Set the Resource Class name to something descriptive (e.g.,
mac-runner) - This creates a resource class identifier:
your-org/mac-runner
Generate a Runner Authentication Token
- After creating the resource class, click "Create New Token"
- Give the token a descriptive name (e.g.,
mac-mini-m4-token) - 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.