Aller au contenu

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'à 6,7 fois moins cher que les runners macOS hébergés par GitHub.

CI/CD 30 min de lecture Mis à jour en octobre 2026

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

Les runners macOS hébergés par GitHub sont pratiques, mais facturés à la minute : le coût augmente avec votre volume de builds. À 0,062 $ par minute (tarif du runner macOS standard de GitHub en octobre 2026), une équipe effectuant 75 heures de builds par mois paie environ 279 $. GitHub ne facture pas les minutes des runners auto-hébergés : un Mac mini M4 dédié de My Remote Mac coûte un forfait de 85 $/mois (USD, HT), sans facturation à la minute, et vous offre le même matériel (voire meilleur) pour une fraction du coût.

Pourquoi utiliser un runner Mac auto-hébergé ?
My Remote Mac auto-hébergé Runner hébergé par GitHub
Coût 85 $/mois (sans facturation à la minute) 0,062 $/min (~279 $/mois pour 75h)
Architecture Apple M4, Mac physique attribué 1:1 VM Apple M1, 3 cœurs, 7 Go de RAM (runner standard)
Vitesse de build ~4 min (même projet) ~12 min (projet moyen)
Cache persistant Oui (disque persistant) Non (éphémère)
Logiciels personnalisés Accès root complet Limité
Jobs simultanés 1 job par runner ; ajoutez des runners ou des Mac pour monter en charge 5 (Free, Pro, Team) / 50 (Enterprise)

Prix My Remote Mac en USD, hors taxes. Chiffres GitHub : runner macOS standard à 0,062 $ par minute, d'après la documentation tarifaire de GitHub, en octobre 2026.

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.

Prérequis

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

  • Un serveur de build Mac mini M4 dédié chez My Remote Mac (à partir de 85 $/mois, USD, HT)
  • 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

É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 the macOS version
sw_vers

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 release of Xcode (asks for your Apple Account)
xcodes install --latest

# Show the installed versions and their paths
xcodes installed
# e.g. 27.0 (27A266a)  /Applications/Xcode-27.0.0.app

# Set it as the active Xcode (interactive SSH session: sudo asks for your password)
sudo xcode-select -s /Applications/Xcode-27.0.0.app/Contents/Developer

# Verify
xcodebuild -version
# Xcode 27.0
# Build version 27A266a

Installer les simulateurs iOS

# Install the iOS simulator runtime that matches your Xcode
xcodebuild -downloadPlatform iOS

# Verify simulator availability
xcrun simctl list runtimes
xcrun simctl list devices available

É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 (macOS ARM64). The same commands, with the
# current version and its checksum, are shown on Settings > Actions > Runners >
# New self-hosted runner
RUNNER_VERSION=$(curl -fsSL https://api.github.com/repos/actions/runner/releases/latest \
  | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
curl -o "actions-runner-osx-arm64-${RUNNER_VERSION}.tar.gz" -L \
  "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-osx-arm64-${RUNNER_VERSION}.tar.gz"

# Extract the package
tar xzf "actions-runner-osx-arm64-${RUNNER_VERSION}.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 et redémarre en cas de plantage. GitHub fournit un script d'installation de service intégré pour macOS :

# Install as a launchd service (a LaunchAgent; no sudo on macOS)
cd ~/actions-runner
./svc.sh install

# Start the service
./svc.sh start

# Check the service status
./svc.sh status

# View the launchd plist (for reference)
cat ~/Library/LaunchAgents/actions.runner.*.plist

Le service démarre désormais à chaque ouverture de session de l'utilisateur du runner et relance le runner si le processus s'arrête. Un LaunchAgent ne démarre pas tout seul au démarrage du Mac : sur un Mac sans écran, activez l'ouverture de session automatique pour cet utilisateur (Réglages Système > Utilisateurs et groupes ; indisponible tant que FileVault est activé) afin que le runner revienne en ligne après un redémarrage. 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

É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]

    # bash -eo pipefail: a failing xcodebuild fails the step even when piped to xcbeautify
    defaults:
      run:
        shell: bash

    env:
      SCHEME: "MyApp"
      # Any device listed by: xcrun simctl list devices available
      DESTINATION: "platform=iOS Simulator,name=iPhone 17,OS=latest"
      # Pick the Xcode for this job without sudo (path from: xcodes installed)
      DEVELOPER_DIR: /Applications/Xcode-27.0.0.app/Contents/Developer
      # Caches sit next to the checkout: actions/checkout cleans the checkout
      # (git clean -ffdx) on every run, but not its parent folder
      DERIVED_DATA: ${{ github.workspace }}/../DerivedData
      SPM_CACHE: ${{ github.workspace }}/../SourcePackages

    steps:
      - name: Checkout code
        uses: actions/checkout@v7

      - name: Show the selected Xcode
        run: xcodebuild -version

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

      - name: Install xcbeautify
        run: command -v xcbeautify >/dev/null || brew install xcbeautify

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

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

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v7
        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'

    env:
      # Same Xcode as the build job (App Store Connect requires Xcode 26 or later)
      DEVELOPER_DIR: /Applications/Xcode-27.0.0.app/Contents/Developer

    steps:
      - name: Checkout code
        uses: actions/checkout@v7

      - 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 (the folder Xcode 16 and later read)
          PP_PATH=$RUNNER_TEMP/build_pp.mobileprovision
          echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
          mkdir -p ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles
          cp $PP_PATH ~/Library/Developer/Xcode/UserData/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 }}
          APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
        run: |
          # altool reads the key from ~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8
          xcrun altool --upload-app \
            -f DerivedData/Export/MyApp.ipa \
            -t ios \
            --apiKey $APP_STORE_CONNECT_API_KEY \
            --apiIssuer $APP_STORE_CONNECT_ISSUER_ID

      # The runner is persistent: remove the signing files at the end of every job
      - name: Clean up keychain and provisioning profile
        if: ${{ always() }}
        run: |
          security delete-keychain $RUNNER_TEMP/app-signing.keychain-db || true
          rm -f ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles/build_pp.mobileprovision

É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, keep DerivedData next to the checkout (job env, as above):
#   DERIVED_DATA: ${{ github.workspace }}/../DerivedData
#   ... -derivedDataPath "$DERIVED_DATA"
# Each repository then gets ~/actions-runner/_work/<repo>/DerivedData

# On the runner, remove the DerivedData of repositories not built for 7 days
# (cron job every Sunday at 03:00, keeps your existing crontab entries)
(crontab -l 2>/dev/null; echo '0 3 * * 0 find "$HOME"/actions-runner/_work/*/DerivedData -maxdepth 0 -type d -mtime +7 -exec rm -rf {} +') \
  | crontab -

Mettre en cache les paquets SPM

# Use clonedSourcePackagesDirPath to keep SPM packages on disk
# (SPM_CACHE points outside the checkout, as in the workflow above)
xcodebuild build \
  -scheme "MyApp" \
  -clonedSourcePackagesDirPath "$SPM_CACHE" \
  -derivedDataPath "$DERIVED_DATA"

# This avoids re-downloading packages on every build

Exécution parallèle des tests

# Run tests in parallel across multiple simulators
# (devices from: xcrun simctl list devices available)
set -o pipefail
xcodebuild test \
  -scheme "MyApp" \
  -destination "platform=iOS Simulator,name=iPhone 17,OS=latest" \
  -destination "platform=iOS Simulator,name=iPhone 17 Pro,OS=latest" \
  -parallel-testing-enabled YES \
  -maximum-parallel-testing-workers 4 \
  -derivedDataPath "$DERIVED_DATA" \
  | xcbeautify

Installer xcbeautify pour de meilleurs logs

# xcbeautify formats Xcode output for CI environments
brew install xcbeautify

# Use it by piping xcodebuild output (pipefail keeps xcodebuild's exit status):
set -o pipefail && xcodebuild build -scheme "MyApp" | xcbeautify

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
./svc.sh status

# View logs
cat ~/actions-runner/_diag/Runner_*.log | tail -50

# Restart the service
./svc.sh stop
./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 17"

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 simulators whose runtime is no longer installed
xcrun simctl delete unavailable

# Clean Homebrew cache
brew cleanup --prune=7

# Remove old Xcode archives
rm -rf ~/Library/Developer/Xcode/Archives/*

Analyse des coûts

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

Analyse des coûts
Taille de l'équipe Coût My Remote Mac Builds/mois Coût runner GitHub hébergé Économies mensuelles
Développeur solo 85 $/mois 100 builds (10 min moy.) 62 $/mois Aucune (coûte 23 $/mois de plus)
Petite équipe (5) 85 $/mois 500 builds (10 min moy.) 310 $/mois 225 $/mois
Équipe moyenne (15) 229 $/mois (M4 Pro) 1500 builds (10 min moy.) 930 $/mois 701 $/mois
Entreprise (50+) 458 $/mois (2x M4 Pro) 5000+ builds 3 100+ $/mois 2 642+ $/mois

Prix My Remote Mac en USD, hors taxes. Chiffres GitHub : runner macOS standard à 0,062 $ par minute, d'après la documentation tarifaire de GitHub, en octobre 2026.

En résumé : Pour les équipes effectuant plus de ~140 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. À 100 builds par mois, comme dans la ligne Développeur solo, les runners hébergés par GitHub coûtent 23 $/mois de moins ; un développeur solo peut tout de même préférer un Mac dédié pour ses caches persistants, ses builds plus rapides et l'absence de facturation à la minute. Si vous ne publiez qu'occasionnellement, l'offre à la journée vous donne un Mac pour le jour de la publication dès 14 $ par 24 heures (USD, hors taxes) : il s'arrête au bout de 24 heures sauf si vous activez le renouvellement automatique, et vous pouvez passer au mensuel sur le même Mac.

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

Déployez un Mac Mini M4 dédié comme runner GitHub Actions. À partir de 85 $/mois.

Prix My Remote Mac en USD, hors taxes.

Besoin de plus de détails ?

Parcourez la documentation complète : guides pas à pas, références de configuration et dépannage.

Ouvrir la documentation →