1. Perché usare un Mac Runner self-hosted?
I runner macOS ospitati da GitHub sono comodi ma costosi. A 0,08 $ al minuto, un team che esegue 75 ore di build al mese paga circa 360 $. Un Mac Mini M4 dedicato di MyRemoteMac costa 85 $/mese con minuti di build illimitati, offrendoti lo stesso hardware (o migliore) a una frazione del costo.
| Caratteristica | Runner ospitato da GitHub | MyRemoteMac Self-Hosted |
|---|---|---|
| Costo | 0,08 $/min (~350 $/mese per 75 h) | 85 $/mese (minuti illimitati) |
| Architettura | Intel x86 (alcuni M1) | Apple M4 (ultimo) |
| Velocità di build | ~12 min (progetto medio) | ~4 min (stesso progetto) |
| Cache persistente | No (effimera) | Sì (disco persistente) |
| Software personalizzato | Limitato | Accesso root completo |
| Job simultanei | 5 (gratis) / 20 (a pagamento) | Illimitati (il tuo hardware) |
Vantaggio chiave: Poiché il runner è persistente, DerivedData, cache SPM e CocoaPods vengono conservati tra le build. Solo questo può ridurre i tempi di build del 50-70% rispetto ai runner effimeri ospitati da GitHub che ripartono da zero ogni volta.
2. Prerequisiti
Prima di iniziare, assicurati di avere quanto segue:
- Un server Mac Mini M4 di MyRemoteMac (da 85 $/mese)
- Un account GitHub con accesso admin al tuo repository o alla tua organizzazione
- Accesso SSH al tuo Mac Mini (incluso nel tuo abbonamento MyRemoteMac)
- Un account Apple Developer (per la firma del codice e i profili di provisioning)
- Familiarità di base con YAML e comandi da terminale
3. Passo 1: Connettiti al tuo Mac Mini via SSH e installa Xcode
Per prima cosa, connettiti al tuo Mac Mini M4 via SSH. Hai ricevuto le tue credenziali quando hai configurato il tuo server MyRemoteMac.
Connettiti 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
Installa gli Xcode Command Line Tools
# Install Command Line Tools
xcode-select --install
# Accept the license agreement
sudo xcodebuild -license accept
# Verify installation
xcode-select -p
# /Library/Developer/CommandLineTools
Installa Xcode (versione completa)
Per le build iOS ti serve l'applicazione Xcode completa. Il modo più veloce per installarla su un server headless è usare xcodes:
# 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 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
xcodebuild -version
# Xcode 16.2
# Build version 16C5032a
Installa i simulatori iOS
# 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
4. Passo 2: Installa il GitHub Actions Runner
Ora scarichiamo e configuriamo l'agente GitHub Actions runner. Vai al tuo repository su GitHub, apri Settings > Actions > Runners > New self-hosted runner e seleziona macOS + ARM64.
Scarica e configura
# Create a directory for the runner
mkdir -p ~/actions-runner && cd ~/actions-runner
# Download the latest runner package (ARM64)
curl -o actions-runner-osx-arm64-2.321.0.tar.gz -L \
https://github.com/actions/runner/releases/download/v2.321.0/actions-runner-osx-arm64-2.321.0.tar.gz
# Extract the package
tar xzf actions-runner-osx-arm64-2.321.0.tar.gz
# Configure the runner
# Replace YOUR_TOKEN with the token from GitHub Settings
./config.sh --url https://github.com/YOUR_ORG/YOUR_REPO \
--token YOUR_TOKEN \
--name "mac-mini-m4-runner" \
--labels "self-hosted,macOS,ARM64,M4" \
--work "_work"
# Test the runner interactively first
./run.sh
Installa come servizio launchd persistente
Eseguire l'agente in modo interattivo va bene per i test, ma in produzione deve avviarsi automaticamente all'avvio e riavviarsi in caso di crash. GitHub fornisce uno script integrato di installazione del servizio per macOS:
# Install as a launchd service
cd ~/actions-runner
sudo ./svc.sh install
# Start the service
sudo ./svc.sh start
# Check the service status
sudo ./svc.sh status
# Expected: "active (running)"
# View the launchd plist (for reference)
cat /Library/LaunchDaemons/actions.runner.*.plist
Il servizio ora si avvia automaticamente all'avvio e si riavvia se il processo si interrompe. Puoi verificare che il runner appaia come "Idle" nella pagina Settings > Actions > Runners del tuo repository GitHub.
Configura il runner per più repository (a livello di organizzazione)
# For an organization-level runner, use the organization URL:
./config.sh --url https://github.com/YOUR_ORG \
--token YOUR_ORG_TOKEN \
--name "mac-mini-m4-org-runner" \
--labels "self-hosted,macOS,ARM64,M4" \
--runnergroup "Default" \
--work "_work"
# This allows ALL repositories in your organization to use this runner
5. Passo 3: Crea il tuo workflow di build iOS
Crea un file di workflow nel tuo repository in .github/workflows/ios-build.yml. Questo workflow verrà eseguito sul tuo self-hosted Mac Mini M4 runner.
name: iOS Build & Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: [self-hosted, macOS, ARM64, M4]
env:
SCHEME: "MyApp"
DESTINATION: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2"
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Select Xcode version
run: |
sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
xcodebuild -version
- name: Resolve Swift Package Dependencies
run: |
xcodebuild -resolvePackageDependencies \
-scheme "$SCHEME" \
-clonedSourcePackagesDirPath .spm-cache
- name: Build the app
run: |
xcodebuild build \
-scheme "$SCHEME" \
-destination "$DESTINATION" \
-clonedSourcePackagesDirPath .spm-cache \
-derivedDataPath DerivedData \
| xcbeautify
- name: Run unit tests
run: |
xcodebuild test \
-scheme "$SCHEME" \
-destination "$DESTINATION" \
-clonedSourcePackagesDirPath .spm-cache \
-derivedDataPath DerivedData \
-resultBundlePath TestResults.xcresult \
| xcbeautify
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: TestResults.xcresult
Aggiungi la firma del codice per le build di release
Per i deployment su TestFlight o App Store, aggiungi i passaggi di firma del codice. Salva i tuoi certificati e profili di provisioning come GitHub Secrets:
deploy:
needs: build
runs-on: [self-hosted, macOS, ARM64, M4]
if: github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install certificate and provisioning profile
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
# Create a temporary keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# Import certificate
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" \
-A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# Install provisioning profile
PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- name: Build for distribution
run: |
xcodebuild archive \
-scheme "MyApp" \
-archivePath DerivedData/MyApp.xcarchive \
-destination "generic/platform=iOS" \
CODE_SIGN_STYLE=Manual
- name: Export IPA
run: |
xcodebuild -exportArchive \
-archivePath DerivedData/MyApp.xcarchive \
-exportOptionsPlist ExportOptions.plist \
-exportPath DerivedData/Export
- name: Upload to TestFlight
env:
APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
run: |
xcrun altool --upload-app \
-f DerivedData/Export/MyApp.ipa \
-t ios \
--apiKey $APP_STORE_CONNECT_API_KEY
6. Passo 4: Ottimizza le prestazioni
Uno dei maggiori vantaggi di un self-hosted runner è il caching persistente. Ecco le ottimizzazioni chiave per sfruttare al massimo il tuo Mac Mini M4.
Abilita il caching di DerivedData
Poiché il runner è persistente, DerivedData viene conservato tra le build. Usa un percorso DerivedData coerente:
# In your workflow, always use:
-derivedDataPath DerivedData
# On the runner, periodically clean old DerivedData to save space:
# Add a cron job to clean builds older than 7 days
echo "0 3 * * 0 find ~/actions-runner/_work/*/DerivedData -maxdepth 0 -mtime +7 -exec rm -rf {} +" \
| crontab -
Metti in cache i pacchetti SPM
# Use clonedSourcePackagesDirPath to keep SPM packages on disk
xcodebuild build \
-scheme "MyApp" \
-clonedSourcePackagesDirPath ~/spm-cache \
-derivedDataPath DerivedData
# This avoids re-downloading packages on every build
Esecuzione dei test in parallelo
# Run tests in parallel across multiple simulators
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
Installa xcbeautify per log migliori
# xcbeautify formats Xcode output for CI environments
brew install xcbeautify
# Use it by piping xcodebuild output:
xcodebuild build -scheme "MyApp" | xcbeautify
7. Risoluzione dei problemi comuni
Il runner appare "Offline" in GitHub
Di solito significa che il servizio launchd non è in esecuzione. Controlla lo stato del servizio e i log:
# Check service status
sudo ./svc.sh status
# View logs
cat ~/actions-runner/_diag/Runner_*.log | tail -50
# Restart the service
sudo ./svc.sh stop
sudo ./svc.sh start
La firma del codice fallisce con "No signing certificate"
Il servizio launchd viene eseguito in un contesto utente diverso. Assicurati che il portachiavi sia accessibile:
# Ensure the login keychain is unlocked for the runner user
security unlock-keychain -p "YOUR_PASSWORD" ~/Library/Keychains/login.keychain-db
# Or use a dedicated keychain in your workflow (recommended)
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
Il simulatore non si avvia
A volte i simulatori si bloccano. Reimpostali tra una build e l'altra:
# Shutdown all running simulators
xcrun simctl shutdown all
# Erase all simulator data (nuclear option)
xcrun simctl erase all
# Boot a specific simulator
xcrun simctl boot "iPhone 16 Pro"
Spazio su disco in esaurimento
Le build di Xcode generano molti dati. Configura una pulizia automatica:
# 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 Xcode archives
rm -rf ~/Library/Developer/Xcode/Archives/*
8. Analisi dei costi
Ecco un confronto dettagliato dei costi per diverse dimensioni di team e volumi di build:
| Dimensione del team | Build/mese | Costo ospitato da GitHub | Costo MyRemoteMac | Risparmio mensile |
|---|---|---|---|---|
| Sviluppatore singolo | 100 build (10 min medi) | 80 $/mese | 85 $/mese | 5 $/mese |
| Piccolo team (5) | 500 build (10 min medi) | 400 $/mese | 85 $/mese | 325 $/mese |
| Team medio (15) | 1500 build (10 min medi) | 1.200 $/mese | 229 $/mese (M4 Pro) | 971 $/mese |
| Enterprise (50+) | 5000+ build | 4.000+ $/mese | 458 $/mese (2x M4 Pro) | 3.542+ $/mese |
In sintesi: Per i team che eseguono più di ~100 build al mese, un Mac Mini M4 self-hosted si ripaga immediatamente. E poiché le build sono più veloci su hardware persistente con cache calde, il tuo team risparmia anche tempo di sviluppo.
Guide correlate
Jenkins Mac Build Agent
Configura agenti di build Jenkins su un Mac Mini dedicato per il CI/CD iOS.
Fastlane + server Mac dedicato
Automatizza build iOS, screenshot e deployment su TestFlight con Fastlane.
GitLab CI Mac Runner
Registra un GitLab Runner su un Mac Mini dedicato per le pipeline CI iOS/macOS.