1. Pourquoi des runners GitLab auto-hébergés sur Mac ?
GitLab propose des runners partagés sur Linux, mais compiler des applications iOS nécessite macOS sur du matériel Apple. Les runners macOS partagés de GitLab sont limités et coûteux. Un runner Mac Mini M4 auto-hébergé vous offre :
L'offre gratuite de GitLab inclut 400 minutes CI/CD sur les runners partagés. En auto-hébergé, il n'y a aucune limite.
Compilez sur la même puce M4 que celle des appareils de vos utilisateurs. Aucune surcharge de traduction Rosetta.
Installez n'importe quelle version de Xcode, des simulateurs, des outils et des dépendances dont vous avez besoin.
DerivedData, les paquets SPM et les caches CocoaPods survivent entre les exécutions de pipeline.
2. Installer le runner GitLab
Connectez-vous en SSH à votre Mac Mini M4 et installez le runner GitLab avec Homebrew :
# Connect to your Mac Mini M4
ssh admin@your-server-ip
# Install Homebrew (if not already installed)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> ~/.zprofile
eval "$(/opt/homebrew/bin/brew shellenv)"
# Install GitLab Runner
brew install gitlab-runner
# Verify installation
gitlab-runner --version
# Version: 17.7.0
# Git revision: ...
# Git branch: 17-7-stable
# GO version: go1.22.10
# Built: ...
# OS/Arch: darwin/arm64
Installer en tant que service macOS
# Install the runner as a launchd service
# This ensures it starts automatically on boot
brew services start gitlab-runner
# Verify the service is running
brew services list | grep gitlab-runner
# gitlab-runner started admin ~/Library/LaunchAgents/homebrew.mxcl.gitlab-runner.plist
# Check runner status
gitlab-runner status
# gitlab-runner: Service is running
3. Enregistrer le runner
Accédez à votre projet (ou groupe) GitLab et naviguez vers Settings > CI/CD > Runners > New project runner. Copiez le jeton d'enregistrement.
Enregistrer avec le nouveau flux d'enregistrement (GitLab 16+)
# Register the runner using the authentication token from GitLab UI
# (GitLab 16+ uses authentication tokens instead of registration tokens)
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--token "YOUR_RUNNER_AUTHENTICATION_TOKEN" \
--executor "shell" \
--description "mac-mini-m4-runner" \
--tag-list "macos,apple-silicon,m4,ios,xcode"
Enregistrer avec le jeton d'enregistrement legacy (GitLab 15 et antérieur)
# For older GitLab instances using registration tokens
gitlab-runner register \
--non-interactive \
--url "https://gitlab.com/" \
--registration-token "YOUR_REGISTRATION_TOKEN" \
--executor "shell" \
--description "mac-mini-m4-runner" \
--tag-list "macos,apple-silicon,m4,ios,xcode" \
--run-untagged="false"
Vérifier la configuration du runner
# View the runner config file
cat ~/.gitlab-runner/config.toml
# Expected output:
# concurrent = 2
# check_interval = 0
#
# [session_server]
# session_timeout = 1800
#
# [[runners]]
# name = "mac-mini-m4-runner"
# url = "https://gitlab.com/"
# token = "..."
# executor = "shell"
# [runners.cache]
# MaxUploadedArchiveSize = 0
# Adjust concurrency based on your hardware:
# Mac Mini M4 (16GB): concurrent = 2
# Mac Mini M4 Pro (24GB): concurrent = 3
# Mac Mini M4 Pro (48GB): concurrent = 4
Éditez ~/.gitlab-runner/config.toml pour ajuster la concurrence :
# Edit the config
nano ~/.gitlab-runner/config.toml
# Set concurrent to match your hardware capacity
concurrent = 2
# Restart the runner to apply changes
gitlab-runner restart
Le runner devrait maintenant apparaître comme En ligne dans votre projet GitLab sous Settings > CI/CD > Runners.
4. Créer un .gitlab-ci.yml pour iOS
Créez un fichier .gitlab-ci.yml à la racine de votre dépôt. Ce pipeline complet compile, teste et déploie votre application iOS :
# .gitlab-ci.yml
stages:
- setup
- build
- test
- deploy
variables:
SCHEME: "MyApp"
WORKSPACE: "MyApp.xcworkspace"
DESTINATION: "platform=iOS Simulator,name=iPhone 16 Pro,OS=18.2"
DERIVED_DATA: "${CI_PROJECT_DIR}/DerivedData"
# Only run on our Mac runner
default:
tags:
- macos
- m4
# ---- SETUP ----
setup:
stage: setup
script:
- sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
- xcodebuild -version
- swift --version
# Install CocoaPods if using Podfile
- |
if [ -f "Podfile" ]; then
pod install --repo-update
fi
cache:
key: pods-${CI_COMMIT_REF_SLUG}
paths:
- Pods/
- .spm-cache/
# ---- BUILD ----
build:
stage: build
needs: ["setup"]
script:
- |
xcodebuild build \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-clonedSourcePackagesDirPath ".spm-cache" \
CODE_SIGNING_ALLOWED=NO \
| xcbeautify
cache:
key: derived-data-${CI_COMMIT_REF_SLUG}
paths:
- DerivedData/
- .spm-cache/
artifacts:
paths:
- DerivedData/
expire_in: 1 hour
# ---- TEST ----
unit_tests:
stage: test
needs: ["build"]
script:
- |
xcodebuild test \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-resultBundlePath "TestResults.xcresult" \
-parallel-testing-enabled YES \
| xcbeautify
artifacts:
when: always
paths:
- TestResults.xcresult/
reports:
junit: TestResults.xcresult/report.junit
expire_in: 7 days
after_script:
- xcrun simctl shutdown all 2>/dev/null || true
# ---- DEPLOY ----
deploy_testflight:
stage: deploy
needs: ["unit_tests"]
only:
- main
script:
- |
# Install or update Fastlane
which fastlane || brew install fastlane
# Run Fastlane beta lane
fastlane beta
environment:
name: testflight
variables:
MATCH_PASSWORD: ${MATCH_PASSWORD}
APP_STORE_CONNECT_API_KEY_ID: ${APP_STORE_KEY_ID}
APP_STORE_CONNECT_API_ISSUER_ID: ${APP_STORE_ISSUER_ID}
APP_STORE_CONNECT_API_KEY_CONTENT: ${APP_STORE_KEY_CONTENT}
Ajouter des variables CI/CD dans GitLab
Naviguez vers Settings > CI/CD > Variables dans votre projet GitLab et ajoutez ces variables en tant que "Masquées" et "Protégées" :
-
MATCH_PASSWORD- Mot de passe pour le chiffrement Fastlane Match -
APP_STORE_KEY_ID- ID de clé API App Store Connect -
APP_STORE_ISSUER_ID- Issuer ID App Store Connect -
APP_STORE_KEY_CONTENT- Contenu du fichier de clé .p8
5. Optimiser les performances
Configuration du cache GitLab
Le runner GitLab prend en charge le cache local pour les exécuteurs shell. Puisque votre runner est persistant, les caches locaux sont extrêmement efficaces :
# In .gitlab-ci.yml, configure cache per branch:
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- DerivedData/
- .spm-cache/
- Pods/
policy: pull-push
# For test jobs that don't modify cache, use pull-only:
unit_tests:
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- DerivedData/
policy: pull
Utiliser les artefacts pour les données inter-stages
# Pass build artifacts between stages efficiently
build:
artifacts:
paths:
- DerivedData/Build/Products/
expire_in: 2 hours
# The test stage receives the built products without rebuilding
test:
needs: ["build"] # only download artifacts from the build job
script:
- xcodebuild test-without-building \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}"
Exécution parallèle des tests
# Split tests across parallel jobs using GitLab's parallel keyword
unit_tests:
stage: test
parallel: 2
script:
- |
# Use test plan partitioning or custom splitting
xcodebuild test \
-workspace "${WORKSPACE}" \
-scheme "${SCHEME}" \
-destination "${DESTINATION}" \
-derivedDataPath "${DERIVED_DATA}" \
-parallel-testing-enabled YES \
-maximum-parallel-testing-workers 4
Nettoyage planifié du cache
# On the Mac Mini, set up a weekly cleanup cron job
crontab -e
# Add these lines:
# Clean DerivedData older than 7 days every Sunday at 3 AM
0 3 * * 0 find ~/builds/*/DerivedData -maxdepth 0 -mtime +7 -exec rm -rf {} + 2>/dev/null
# Clean old GitLab Runner builds older than 14 days
0 4 * * 0 find ~/builds -maxdepth 2 -mtime +14 -type d -exec rm -rf {} + 2>/dev/null
# Clean Homebrew cache monthly
0 5 1 * * /opt/homebrew/bin/brew cleanup --prune=30 2>/dev/null
6. Résolution des problèmes
Le runner apparaît "hors ligne" dans GitLab
Vérifiez l'état du service du runner et les journaux :
# Check service status
brew services list | grep gitlab-runner
# View logs
cat /usr/local/var/log/gitlab-runner.log
# Restart the service
brew services restart gitlab-runner
# Verify connectivity to GitLab
gitlab-runner verify
Erreurs de permission refusée pendant les builds
Le runner peut avoir besoin d'accéder au répertoire développeur Xcode :
# Ensure the runner user has Xcode access
sudo xcode-select -s /Applications/Xcode-16.2.app/Contents/Developer
sudo xcodebuild -license accept
# If using simulators, ensure the user can access them
xcrun simctl list devices
Le cache n'est pas restauré
Assurez-vous que les clés de cache sont cohérentes et que les chemins existent :
# Check cache directory permissions
ls -la ~/builds/
# The shell executor stores caches locally by default
# Verify the cache directory in config.toml:
cat ~/.gitlab-runner/config.toml
# Ensure [runners.cache] section has the right settings
# For local caching (most efficient for persistent runners):
# [runners.cache]
# Type = "" # empty = local cache
Le build Xcode se bloque ou expire
C'est souvent causé par des invites d'accès au trousseau ou des problèmes de simulateur :
# Unlock the keychain before builds
security unlock-keychain -p "YOUR_PASSWORD" ~/Library/Keychains/login.keychain-db
# Kill stuck simulators
xcrun simctl shutdown all
pkill -f "Simulator.app" 2>/dev/null || true
# Set a build timeout in .gitlab-ci.yml
build:
timeout: 30 minutes
7. FAQ
Puis-je utiliser un exécuteur Docker sur macOS ?
Docker sur macOS exécute des conteneurs Linux dans une VM, qui ne peut pas accéder aux API macOS, à Xcode ni aux simulateurs iOS. Pour les builds iOS, vous devez utiliser l'exécuteur shell. Docker est adapté pour le Swift côté serveur ou d'autres tâches Linux exécutées en parallèle de votre runner Mac.
Comment enregistrer le runner pour un groupe GitLab ?
Allez dans Settings > CI/CD > Runners > New group runner de votre groupe GitLab. Utilisez le jeton de runner de groupe au lieu d'un jeton de projet. Cela rend le runner disponible pour tous les projets du groupe.
Dois-je utiliser l'exécuteur shell ou l'exécuteur SSH ?
Utilisez l'exécuteur shell. Il exécute les commandes directement sur le Mac, ce qui donne un accès complet à Xcode, aux simulateurs et au trousseau. L'exécuteur SSH est destiné aux machines distantes, ce qui est inutile lorsque le runner est déjà sur le Mac.
Puis-je exécuter les runners GitLab et GitHub Actions sur le même Mac ?
Oui. Les deux runners sont légers et peuvent coexister sur le même Mac Mini M4. Assurez-vous simplement de prendre en compte l'utilisation combinée des ressources lors de la définition des niveaux de concurrence pour chaque runner.
Comment mettre à jour le runner GitLab ?
# Update via Homebrew
brew upgrade gitlab-runner
# Restart the service
brew services restart gitlab-runner
# Verify the new version
gitlab-runner --version