Guide - CI/CD

Comment configurer un runner GitHub Actions auto-hébergé sur Mac Mini M4

Guide étape par étape pour configurer un runner GitHub Actions auto-hébergé sur un Mac Mini M4 dédié. Des builds iOS plus rapides, Apple Silicon natif et jusqu'à 10 fois moins cher que les runners macOS hébergés par GitHub.

30 min de lecture Mis à jour en janvier 2025

1. Pourquoi utiliser un runner Mac auto-hébergé ?

Les runners macOS hébergés par GitHub sont pratiques mais coûteux. À 0,08 $ par minute, une équipe effectuant 75 heures de builds par mois paie environ 360 $. Un Mac Mini M4 dédié de My Remote Mac coûte 75 $/mois avec des minutes de build illimitées, vous offrant le même matériel (voire meilleur) pour une fraction du coût.

Caractéristique Runner hébergé par GitHub My Remote Mac auto-hébergé
Coût 0,08 $/min (~350 $/mois pour 75h) 75 $/mois (minutes illimitées)
Architecture Intel x86 (quelques M1) Apple M4 (dernière génération)
Vitesse de build ~12 min (projet moyen) ~4 min (même projet)
Cache persistant Non (éphémère) Oui (disque persistant)
Logiciels personnalisés Limité Accès root complet
Jobs simultanés 5 (gratuit) / 20 (payant) Illimités (votre matériel)

Avantage clé : Comme le runner est persistant, DerivedData, les caches SPM et CocoaPods sont préservés entre les builds. Cela seul peut réduire les temps de build de 50 à 70 % par rapport aux runners GitHub hébergés éphémères qui repartent de zéro à chaque fois.

2. Prérequis

Avant de commencer, assurez-vous de disposer des éléments suivants :

  • Un serveur Mac Mini M4 chez My Remote Mac (à partir de 75 $/mois)
  • Un compte GitHub avec un accès admin à votre dépôt ou organisation
  • Un accès SSH à votre Mac Mini (fourni avec votre abonnement My Remote Mac)
  • Un compte Apple Developer (pour la signature de code et les profils de provisionnement)
  • Une connaissance de base de YAML et des commandes terminal

3. Étape 1 : Se connecter en SSH à votre Mac Mini et installer Xcode

Commencez par vous connecter à votre Mac Mini M4 via SSH. Vous aurez reçu vos identifiants lors de la mise en place de votre serveur My Remote Mac.

Connexion 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

Installer les outils en ligne de commande Xcode

# Install Command Line Tools
xcode-select --install

# Accept the license agreement
sudo xcodebuild -license accept

# Verify installation
xcode-select -p
# /Library/Developer/CommandLineTools

Installer Xcode (version complète)

Pour les builds iOS, vous avez besoin de l'application Xcode complète. Le moyen le plus rapide de l'installer sur un serveur sans écran est d'utiliser 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

Installer les simulateurs 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. Étape 2 : Installer le runner GitHub Actions

Téléchargeons et configurons maintenant l'agent runner GitHub Actions. Accédez à votre dépôt sur GitHub, allez dans Settings > Actions > Runners > New self-hosted runner, et sélectionnez macOS + ARM64.

Télécharger et configurer

# 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

Installer en tant que service launchd persistant

Exécuter l'agent de manière interactive convient pour les tests, mais pour la production, il faut qu'il démarre automatiquement au démarrage et redémarre en cas de plantage. GitHub fournit un script d'installation de service intégré pour 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

Le service démarrera désormais automatiquement au démarrage et redémarrera si le processus s'arrête. Vous pouvez vérifier que le runner apparaît comme "Idle" dans la page Settings > Actions > Runners de votre dépôt GitHub.

Configurer le runner pour plusieurs dépôts (niveau organisation)

# 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. Étape 3 : Créer votre workflow de build iOS

Créez un fichier workflow dans votre dépôt à l'emplacement .github/workflows/ios-build.yml. Ce workflow s'exécutera sur votre runner Mac Mini M4 auto-hébergé.

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

Ajouter la signature de code pour les builds de release

Pour les déploiements TestFlight ou App Store, ajoutez des étapes de signature de code. Stockez vos certificats et profils de provisionnement en tant que 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. Étape 4 : Optimiser les performances

L'un des plus grands avantages d'un runner auto-hébergé est le cache persistant. Voici les optimisations clés pour tirer le meilleur parti de votre Mac Mini M4.

Activer le cache DerivedData

Puisque le runner est persistant, DerivedData est préservé entre les builds. Utilisez un chemin DerivedData cohérent :

# 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 -

Mettre en cache les paquets 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

Exécution parallèle des tests

# 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

Installer xcbeautify pour de meilleurs logs

# xcbeautify formats Xcode output for CI environments
brew install xcbeautify

# Use it by piping xcodebuild output:
xcodebuild build -scheme "MyApp" | xcbeautify

7. Résolution des problèmes courants

Le runner apparaît "Hors ligne" dans GitHub

Cela signifie généralement que le service launchd n'est pas en cours d'exécution. Vérifiez l'état du service et les journaux :

# 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 signature de code échoue avec "No signing certificate"

Le service launchd s'exécute sous un contexte utilisateur différent. Assurez-vous que le trousseau est accessible :

# 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

Le simulateur ne démarre pas

Les simulateurs restent parfois bloqués. Réinitialisez-les entre les builds :

# 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"

Espace disque insuffisant

Les builds Xcode génèrent beaucoup de données. Configurez un nettoyage automatique :

# 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. Analyse des coûts

Voici une comparaison détaillée des coûts pour différentes tailles d'équipe et volumes de builds :

Taille de l'équipe Builds/mois Coût runner GitHub hébergé Coût My Remote Mac Économies mensuelles
Développeur solo 100 builds (10 min moy.) 80 $/mois 75 $/mois 5 $/mois
Petite équipe (5) 500 builds (10 min moy.) 400 $/mois 75 $/mois 325 $/mois
Équipe moyenne (15) 1500 builds (10 min moy.) 1 200 $/mois 179 $/mois (M4 Pro) 1 021 $/mois
Entreprise (50+) 5000+ builds 4 000+ $/mois 358 $/mois (2x M4 Pro) 3 642+ $/mois

En résumé : Pour les équipes effectuant plus de ~100 builds par mois, un Mac Mini M4 auto-hébergé est immédiatement rentable. Et comme les builds sont plus rapides sur du matériel persistant avec des caches préchauffés, votre équipe gagne aussi du temps de développement.

Guides connexes

Prêt à accélérer vos builds iOS ?

Déployez un Mac Mini M4 dédié comme runner GitHub Actions. À partir de 75 $/mois avec un essai gratuit de 7 jours.