VPSSpark Cloud Mac CI série #9. Question récurrente dans les équipes Flutter en France et en Europe : avec 8 à 15 développeurs, deux Cloud Mac suffisent-elles pour une CI stable ? Oui — à condition de séparer Android et iOS : les builds Android restent sur le ubuntu-latest hébergé par GitHub, côté macOS deux runners self-hosted répartis entre macos-fast et macos-archive, et la règle d’or : pas d’archive sur les PR. Ci-dessous : modèle de charge, couches de jobs, extrait de workflow et checklist de mise en prod. Diagnostic des files d’attente et branchement des runners : série #2 et retour d’expérience trois Cloud Mac pour un fort volume iOS CI.
1 · Conclusion contre-intuitive : le goulot Flutter CI n’est pas Dart
La plupart des CI Flutter en échec ne traînent pas sur flutter test, mais parce que les jobs iOS saturent les slots macOS — souvent après un flutter build ipa lancé par erreur sur une PR.
Le cross-platform laisse croire qu’un seul workflow suffit. En pratique, flutter analyze et les tests unitaires tournent aussi sous Linux ; macOS est indispensable pour CocoaPods, la compilation Xcode, la signature et l’export IPA. Si un job archive entre dans le chemin PR, un Cloud Mac reste bloqué 25–40 minutes et toutes les autres PR passent en Queued — le même piège que les équipes iOS natives ; formule de diagnostic : wait time >> run time (voir diagnostic file d’attente macOS runner).
Deux Cloud Mac ne sont donc pas « deux machines universelles », mais une topologie bi-pool volontaire : feedback rapide d’un côté, tâches release de l’autre. Les APK/AAB restent sur Linux — ne pas gaspiller les slots macOS sur Gradle.
2 · Modèle de charge : combien de parallélisme macOS pour 8–15 personnes ?
Référence : équipe Flutter moyennement active (1–2 PR par personne et par jour, nightly sur main, release hebdomadaire) :
Sur le pool rapide (macos-fast) : analyze, tests, build simulateur iOS — objectif 8–14 minutes avec cache pub/DerivedData. Sur le pool archive (macos-archive) : IPA release + notarisation — 20–35 minutes, mais peu fréquent (main, tags, nightly). Pools mélangés : l’archive étouffe le fast ; séparés, le P95 d’attente PR reste souvent sous 10 minutes — suffisant pour le rythme de revue de code en équipe distribuée EU.
Au-delà de 15 personnes ou avec beaucoup de flavors en matrix monorepo, une troisième machine archive peut se justifier — planification comme dans le scénario 500 builds/jour, hors scope de ce démarrage à deux machines.
| Plateforme | Runner | Job type | Sur PR ? |
|---|---|---|---|
| Android | ubuntu-latest (hébergé) |
flutter build apk/appbundle, tests Android |
Oui |
| iOS feedback rapide | Cloud Mac #1 · macos-fast |
analyze, tests, build ios --simulator |
Oui |
| iOS release | Cloud Mac #2 · macos-archive |
build ipa, notarisation, TestFlight |
Non |
3 · Trois niveaux de jobs Flutter : alignés sur les CI Hard Rules
Mapper le pipeline Flutter sur L0/L1/L2 — cohérent avec le manuel on-call :
| Niveau | Contenu Flutter | Pool | Déclenchement PR ? |
|---|---|---|---|
| L0 | dart format --set-exit-if-changed, flutter analyze, tests légers |
Linux ou macos-fast |
Oui |
| L1 | Tests d’intégration, flutter build ios --simulator, widget tests |
macos-fast |
Oui · pas d’archive |
| L2 | flutter build ipa, App Store Connect, notarisation |
macos-archive |
Non · main/tag/schedule uniquement |
Trois règles comme en iOS natif : Rule 1 pas de L2 sur PR ; Rule 2 L2 dans un pool isolé ; Rule 3 le fast pool ne doit pas être bloqué par l’archive. Spécificité Flutter : beaucoup de L0 peut tourner sur Linux — tout lier à macos-fast gaspille la concurrence macOS. Sur PR : jobs Linux et macOS en parallèle ; macOS uniquement pour l’indispensable.
4 · Topologie bi-machine : rôles de Cloud Mac #1 et #2
Fig. 1 · Topologie type : deux Cloud Mac pour la CI d’une équipe Flutter
macos-fastL0/L1 · 8–14 min/jobmacos-archiveL2 · main/tag uniquementCloud Mac #1 (pool rapide) : labels [self-hosted, macOS, macos-fast, flutter]. SDK Flutter figé (fvm ou pin dans l’image), Xcode, CocoaPods. Runner en service permanent avec démarrage auto ; cache pub et DerivedData chauds — jobs courts, fort débit.
Cloud Mac #2 (pool archive) : labels [self-hosted, macOS, macos-archive, flutter-release]. Isolation physique — jamais deux runners de pools différents sur le même Mac (sinon Rule 2 est vide). Certificats Distribution et clé API App Store Connect dans le Keychain ; branchement comme dans l’onboarding runner en conditions réelles. Avant release : flutter doctor -v et archive dry-run — ne pas polluer le fast pool.
Même région, même version majeure Xcode — moins de « fast vert, archive rouge ». Cloud Mac : image fixe, lien GitHub stable, pas d’UPS de bureau — adapté au CI 7×24. Sur les limites de sécurité des runners self-hosted : restreindre la machine archive à un repo ou une org.
5 · Exemple de workflow : séparation PR et main
Logique centrale de routage (pipeline complet : cache, secrets, artifacts en plus). Documentation CD Flutter pour la signature iOS ; certificats déjà dans le Keychain archive.
jobs: android-pr: if: github.event_name == 'pull_request' runs-on: ubuntu-latest steps: - run: flutter analyze && flutter test - run: flutter build appbundle --release ios-pr-fast: if: github.event_name == 'pull_request' runs-on: [self-hosted, macOS, macos-fast, flutter] steps: - run: flutter analyze - run: flutter test - run: flutter build ios --simulator --no-codesign ios-release: if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') runs-on: [self-hosted, macOS, macos-archive, flutter-release] steps: - run: flutter build ipa --export-options-plist=ExportOptions.plist # TestFlight / notarisation — L2 uniquement
ios-pr-fast n’inclut volontairement pas build ipa. Build appareil sur PR : uniquement via workflow_dispatch, toujours pool archive — pas en événement PR par défaut. Avec concurrency dans la syntaxe workflow, annuler les anciens runs PR — souvent ~20 % d’attente en moins.
6 · Checklist cache : quatre répertoires à persister
Deux Cloud Mac tiennent mieux avec un bon cache qu’avec le dernier CPU. Persister dans le home du runner (ou snapshot nightly) :
PUB_CACHE/~/.pub-cache— deps Dart ; mise à jour incrémentale sipubspec.lockchange- Répertoire SDK Flutter —
fvmfixe la version ; pas deflutter upgradeen CI ios/Pods+ cache CocoaPods —pod installest souvent la longue traîne iOS- Xcode DerivedData — fort gain avec le même
ios/Podfile.lock
Fast et archive ne partagent pas le même DerivedData — mélanger Debug/Simulateur et Release/Archive provoque des erreurs de link fantômes. Cache Gradle Android sur ubuntu-latest via Actions Cache ; épargner le disque Cloud Mac.
pod repo update — valider d’abord sur archive, puis synchroniser le fast pool. Les PR quotidiennes réutilisent le cache ; pas de flutter pub upgrade par défaut en CI.
7 · Checklist mise en prod : de zéro à deux machines branchées
En 1–2 jours ouvrés pour un PoC :
- 2 Cloud Mac même spec (M4 + 16 Go recommandé ; pics compile iOS souvent 12 Go+)
- #1 : Flutter/Xcode/CocoaPods, runner
macos-fast; #2 : matériel de signature, runnermacos-archive - Scinder les workflows : Android
ubuntu-latest; PR L1 seulement ; main/tag L2 - Persister pub/DerivedData/Pods ; observer 3 jours P95 wait < 10 min
- Si
wait >> runpersiste : vérifier L2 dans les PR avant d’ajouter des machines
PoC possible avec une Cloud Mac pour le fast pool ; archive temporairement sur macos-latest hébergé (accepter la file) — valider la logique, puis deuxième machine pour le SLA release. Cohérent avec « deux machines » : la seconde assure les releases, pas le feedback PR.
8 · FAQ
Tous les tests Flutter sur Linux — une Cloud Mac suffit ?
Sans compile macOS en PR, en théorie oui — la plupart des équipes lancent build ios --simulator pour les plugins natifs. Un Mac pour L1 passe ; l’archive doit rester isolée du feedback rapide, sinon les releases bloquent toutes les PR. Deux machines restent le choix sûr.
Deux Cloud Mac vs deux Mac mini au bureau ?
Topologie identique ; différence opérationnelle : Cloud Mac sans achat matériel, images snapshotables, bande passante GitHub souvent plus stable. Mac bureau si 7×24 et salle serveur ; équipes EU distribuées choisissent souvent Cloud Mac — voir aussi montée en charge à fort volume.
Garder Codemagic ou macOS hébergé GitHub ?
Comme secours archive ou overflow PR. Après self-hosting principal, le macOS hébergé convient si < 4 releases/mois ; équipes Flutter actives économisent souvent à long terme avec leurs runners.
Monorepo multi-apps — comment compter les jobs ?
Matrix × flavors se multiplient. Plus de quatre jobs macOS par PR : élargir le fast pool ou réduire la matrix — ne pas injecter l’archive dans les PR.
Deux Cloud Mac — CI Flutter Android et iOS bien séparées
Android reste sur Linux ; compile iOS, signature et export IPA exigent macOS. Deux Cloud Mac mini M4 avec macos-fast et macos-archive évitent que feedback PR et jobs release se disputent les slots — plus rentable qu’une archive dans chaque PR puis Queued pour tous. Apple Silicon fluidifie flutter build ios et le link Xcode ; ~4 W au repos conviennent au CI 7×24.
Face à une salle Mac de bureau : Cloud Mac par forfait, images reproductibles, lien GitHub stable — idéal pour équipes Flutter distribuées en France et en UE. Environnement Unix natif pour Flutter, CocoaPods, Fastlane sans couche de virtualisation.
Pour migrer la CI iOS de votre équipe Flutter : démarrez avec deux VPSSpark Cloud Mac mini M4 comme topologie minimale — voir les offres, PoC fast pool, puis pool archive pour les releases.