Conclusion d’entrée : ~500 builds iOS CI par jour — la plupart des équipes n’ont pas besoin de huit Mac mini ; trois Cloud Mac Apple Silicon suffisent, à condition de stratifier les jobs dans GitHub Actions et d’isoler les files rapide et release via des Self-hosted Mac Runner, plutôt que de lancer trois archives complètes en parallèle sur chaque machine.
Il y a trois mois, nous avons repris l’environnement Xcode CI d’une équipe iOS. Situation initiale :
- 18 développeurs
- 2 apps (monorepo partagé)
- GitHub Actions comme seule CI
- Environ 500 jobs macOS/jour (PR, tests unitaires, releases nocturnes)
Le cahier des charges prévoyait huit Mac mini en salle pour des runners self-hosted. Deux semaines de logs découpés par type de job ont montré un écart net avec l’intuition : ~70 % validation PR et builds d’intégration, 20 % tests Simulator, ~10 % archive + upload (l’upload TestFlight peut être comptabilisé à part, voir figure 1).
Fig. 1 · Composition d’une journée ~500 builds iOS CI (mesure GitHub Actions sur 2 semaines)
Calcul de capacité : de 91 à 63 heures-machine
500 jobs/jour sur 24 h ≈ 21 jobs/h en moyenne — mais ce sont les pics qui comptent : merges matinaux Europe/Amérique, tempête de PR avant release. Les pics atteignent souvent 3 à 5× la moyenne. Dimensionnez pour la file aux pics, pas pour la moyenne de minuit.
Deuxième variable : durée par job. Tests Simulator : 4–8 min ; build PR avec résolution de deps : 12–20 min ; archive release + notarisation + upload : 25–45 min. P50 sur deux semaines : jobs légers ~6 min, lourds ~35 min. En supposant à tort 30 % d’archives, on conclut « il faut huit Mac » — une fois ventilé, les jobs lourds sont minoritaires sur 500 runs.
Sans cache, estimation 325 légers + 175 lourds ≈ 91 heures-machine/jour, au-delà du plafond physique de trois Mac (72 h). Avec cache DerivedData/SPM, sérialisation L2 et labels de file, jobs légers ~4 min, lourds ~22 min en chemin chaud : besoin ≈ 63 heures-machine. Avec facteur de pic ~1,3, c’est tenable — la base mathématique du « trois suffisent ».
Fig. 3 · Besoin quotidien en heures-machine macOS (modèle 500 jobs/jour)
Stratifier Xcode CI : ne pas mélanger archive et PR dans la même file runner
Les pipelines qui tiennent 500 runs/jour utilisent presque toujours trois niveaux Xcode CI :
- L0 : SwiftLint, compile module — label
macos-fast. - L1 : build PR sans archive — Fast Mac Runner, jusqu’à 2 jobs légers en parallèle par nœud.
- L2 : archive, notarisation, TestFlight — label
macos-archive, 1 job max simultané par machine.
GitLab CI, Buildkite, Jenkins : même logique de labels, pas une file fourre-tout. Élasticité burst Buildkite : agent Mac Buildkite sur Cloud Mac. Mac VPS vs pool runner : guide Mac VPS / macOS dans le cloud.
Trois Cloud Mac : répartition et topologie
Topologie statique à trois nœuds déployée (noms libres, rôles à conserver) :
| Nœud | Label runner | Rôle | Parallélisme |
|---|---|---|---|
| Mac-A | macos-fast |
PR Build, Unit Test | 2 × L0/L1 |
| Mac-B | macos-fast |
Symétrique à Mac-A, file rapide | 2 × L0/L1 |
| Mac-C | macos-archive |
Archive, notarisation, Upload | 1 × L2 |
Fig. 2 · Topologie trois nœuds (GitHub Actions Self-hosted Runner)
Deux nœuds fast = débit ; un release = livraison prévisible. Si la file L2 dépasse le SLO un jour de release, Mac-A peut temporairement porter macos-archive — couper la parallélité L0, sinon Keychain et verrous DerivedData provoquent des échecs de signature aléatoires. Les trois minors Xcode doivent être alignés sur les notes de version Xcode.
Pourquoi les GitHub Actions Mac Runner filent vite
Beaucoup commencent avec les runners macOS hébergés par GitHub et voient des files dès que les PR affluent. Rarement « GitHub est lent », plutôt : (1) plafond de concurrence macOS org-wide ; (2) workflows qui archivent tout par PR ; (3) pas de séparation par type de job sur Self-hosted Mac Runner — les jobs rapides derrière les lents. Avant trois Cloud Mac dédiés, queue_wait P95 > 40 min ; avec double file fast/archive, L1 P95 < 8 min.
Approche courante : trois Cloud Mac en pool self-hosted baseline, runners hébergés uniquement pour semaines open source ou pics extrêmes sur L0 — coût maîtrisé, secrets sans migration hebdo.
Self-hosted Runner vs Xcode Cloud
Xcode Cloud convient aux équipes ancrées Apple, zéro ops agent. Self-hosted Mac Runner convient si vous pilotez files, clés de cache et mélange avec Jenkins/Buildkite interne. Tableau :
| Axe | Xcode Cloud | Cloud Mac + Self-hosted Runner |
|---|---|---|
| Facturation | Forfait minutes + plafond concurrence | Abonnement/jour Cloud Mac, heures-machine maîtrisées |
| File | Plateforme unique | Labels fast/archive maison |
| Secrets | Intégration ASC simple | Match / Keychain — runbook interne |
| Idéal pour | Releases rares, peu de custom | 500+ runs/jour iOS CI/CD |
Signaux de bascule après saturation du forfait minutes : FAQ plafonds Xcode Cloud vs Cloud Mac pour archive.
Pourquoi cette équipe a écarté Xcode Cloud
Xcode Cloud a été évalué ; choix final GitHub Actions + Self-hosted Runner : (1) backend et Android déjà sur GitHub — une seule CI ; (2) clés de cache et matrice monorepo sur mesure ; (3) semaines release au-delà du confort du forfait minutes, file non pilotable. Xcode Cloud n’est pas mauvais — il est mal aligné avec « 500 runs/jour, file sous contrôle ».
Bitrise vs Self-hosted Mac Runner — intuition coût
Bitrise et SaaS mobile DevOps épargnent l’ops agent, facturent la concurrence. Pour 18 personnes, deux apps, ~500 jobs/jour, le coût annuel dépasse souvent l’abonnement trois Cloud Mac, avec archive toujours plafonnée par le tier. Bitrise pour les startups sans envie de runner ; deux semaines d’investissement Self-hosted Mac Runner → amortissement 3–6 mois. Déjà sur Bitrise ? migrer L2 vers un nœud release, pas tout d’un coup.
Agent Mac Buildkite : avantages et limites
Buildkite : queue cloud, agents sur votre matériel — bon burst, artifacts clairs. Inconvénient : couche d’orchestration ; parfois excessif pour trois Mac. PoC client excellent sur burst, GitHub Actions natif retenu (YAML connu). Avec Buildkite existant, même stratégie labels fast/archive — voir l’article Buildkite Cloud Mac.
Cloud Mac vs CI Mac mini locale
Huit Mac mini en salle : CapEx, amortissement, coupure, audit datacenter. Trois Cloud Mac : OpEx prévisible, PoC à la journée, région proche du Git. CI locale si compile >14 h/jour/machine stable sur des années. Cloud Mac CI pour pics variables, semaines prestataires, saison review + notarisation. L’équipe garde deux Mac bureau pour le dev ; Xcode CI lourd dans le cloud — plutôt que huit minis idle.
SLO de file : agrandir avec des données, pas au feeling
Métriques : queue_wait_seconds (P95), run_duration par L0/L1/L2, cache_hit_ratio, l2_concurrent (rarement > 3). Seuils exemple : L1 P95 < 8 min ; L2 P95 < 25 min. Quatrième nœud release seulement si L2 dépasse trois jours d’affilée et cache > 60 %.
Cache : levier des 63 heures-machine
DerivedData : clé branch + version Xcode ; changement lock SPM/CocoaPods bust. Nœuds fast partagent cache read-only ; release stocke DerivedData L2 en local NVMe. Matériel de signature via vault — hors bundle cache. Clés avec minor macOS/Xcode, sinon « hit mais link fail » post-upgrade.
Configuration minimale GitHub Actions Self-hosted Runner
Trois enregistrements : macos-fast ×2, macos-archive ×1. L2 exige concurrency pour éviter cancel croisé :
concurrency:
group: ios-archive-${{ github.ref }}
cancel-in-progress: false
jobs:
archive:
runs-on: [self-hosted, macos-archive]
steps:
- uses: actions/checkout@v4
- run: xcodebuild archive -scheme App -archivePath build/App.xcarchive
Nœud release : user macOS dédié + Match ; script unlock après reboot avant file nocturne. Réf. documentation xcodebuild et Fastlane.
Jour de release : 500 runs deviennent 650
Ordre : (1) stopper L0 non critique ; (2) runners hébergés pour L1 seulement ; (3) quatrième Cloud Mac 48 h à la journée. Monter L2 à 2 parallèles/machine durablement → échecs notarisation aléatoires.
Quand la quatrième machine devient obligatoire
- L2 queue P95 > 40 min une semaine, cache déjà optimisé.
- Monorepo >5 apps sur un release, fenêtre nuit insuffisante.
- Archive complète par PR — corriger la pipeline avant d’acheter.
Anti-patterns
Archive complète par PR ; runners sans labels ; deux archives sur un M4 16 Go ; clés cache sans branche ; succès seul sans queue_wait — tout fait croire que trois machines « ne suffisent pas » et pousse à huit minis.
FAQ : réponses courtes pour la recherche
Combien de Mac pour 500 builds iOS/jour ?
Avec stratification (PR / tests / archive) et cache DerivedData, trois Cloud Mac Apple Silicon suffisent souvent : 2 Fast Mac Runner + 1 Release Mac Runner. Si >50 % des runs sont archive complète, ajuster la pipeline ou planifier un quatrième nœud release.
Combien de jobs en parallèle sur GitHub Actions Mac Runner ?
Sur M4 16 Go : 2 jobs légers (PR, unit test) ou 1 job archive. Éviter « 2× archive » en continu.
Pourquoi ne pas paralléliser fortement les archives ?
Pression mémoire → swap, file disque, verrous Keychain et conflits codesign — timeouts sporadiques, pas erreurs de compile reproductibles.
Cloud Mac moins cher que Xcode Cloud ?
Pour iOS CI/CD haute fréquence, Self-hosted Runner permanent et secrets résidents, trois Cloud Mac sont souvent plus prévisibles que la minute. Releases rares, zéro ops : tester Xcode Cloud d’abord.
Bitrise ou trois Cloud Mac ?
Bitrise épargne l’ops, idéal démarrage rapide. ~500 jobs/jour avec contrôle file/cache : Self-hosted Mac Runner + Cloud Mac gagne souvent — coût annuel plus bas, concurrence L2 à vos règles.
VPSSpark : baseline Cloud Mac 2 Fast + 1 Release
Vous calculez « 500 runs/jour — combien de Mac ? » : deux semaines de logs comme fig. 1, puis valider si 63 heures-machine tiennent pour votre modèle. VPSSpark Cloud Mac mini M4 / M4 Pro — PoC journalier ou abonnement — pour GitHub Actions Self-hosted Mac Runner, PR et archive en files séparées.
Voir les offres Mac cloud ou l’accueil VPSSpark — choisir une région, bucketiser un workflow réel, puis décider si trois suffisent — plutôt que commander huit minis d’emblée.