Accueil / Blog / Build iOS avec GitLab CI : tutoriel Mac distant 2026
ENGINEERING_BLOG · 2026.08.31

Build iOS avec GitLab CI : tutoriel Mac distant 2026

Symptôme : le job Linux passe, mais le job iOS affiche « Xcode introuvable » ou ne peut pas signer l’application.

Solution la plus rapide : affectez les tâches iOS à un Runner macOS dédié, exécuté dans une session utilisateur persistante avec Shell executor, puis validez six indicateurs : session, outil Xcode, isolation, secrets, artefacts et reprise après redémarrage.

Cette méthode s’adresse aux développeurs qui utilisent GitLab pour héberger leur code et qui veulent automatiser le build, les tests, l’Archive, la signature et l’envoi vers TestFlight. Elle convient aussi aux indépendants dont le Runner existant construit déjà Android ou le backend, mais qui ne disposent pas encore d’un Mac réservé à la publication.

SECTION 01Pourquoi un Runner Mac est indispensable pour un build iOS avec GitLab CI ?

Un Runner Linux peut orchestrer le pipeline, exécuter des tests de logique et compiler une partie partagée du projet, mais il ne remplace pas l’environnement macOS nécessaire à Xcode et à la distribution iOS. Le job final doit s’exécuter sur un Mac réel avec Xcode, les SDK requis, les outils de signature et l’accès au trousseau de l’utilisateur.

La documentation GitLab sur l’installation du Runner macOS confirme le fonctionnement du service utilisateur avec LaunchAgent. Le point important est qu’un Runner n’est pas seulement installé sur une machine : il doit être démarré dans le bon compte et dans une session capable d’accéder aux ressources demandées par le pipeline.

Trois limites sont souvent confondues :

  • un Runner enregistré n’est pas nécessairement disponible après un redémarrage ;
  • un processus lancé sans la session attendue peut ne pas retrouver le trousseau ou certaines autorisations ;
  • plusieurs versions de Xcode peuvent être installées sans que le job utilise celle qui a été testée.

Le Shell executor est généralement le choix le plus direct pour ce scénario. Les commandes s’exécutent sur l’hôte macOS et peuvent appeler Xcode, xcodebuild, le trousseau et les outils installés dans le compte de service. GitLab précise toutefois que cet executor fournit une isolation limitée : un script peut accéder à davantage de ressources de l’hôte qu’un processus correctement cloisonné. La documentation officielle du Shell executor doit donc être considérée comme une limite de sécurité.

Une session graphique doit-elle rester ouverte ?

Oui, vous devez traiter la session utilisateur comme un prérequis lorsque la signature, le trousseau ou une étape de votre chaîne dépend d’un contexte graphique. Vous n’avez pas besoin de manipuler le Mac pendant tout le pipeline, mais le compte du Runner doit pouvoir ouvrir sa session, charger son trousseau et démarrer le service dans des conditions reproductibles.

L’ouverture de session automatique peut simplifier le démarrage, mais elle n’est pas l’unique solution et augmente les exigences de sécurité physique. Une autre possibilité consiste à conserver une session dédiée ouverte, avec un accès distant contrôlé et un compte différent de votre compte personnel.

Après un redémarrage, considérez le Mac comme prêt uniquement si :

  1. le compte de service ouvre sa session selon la procédure définie ;
  2. le LaunchAgent démarre le Runner ;
  3. GitLab affiche le Runner comme disponible ;
  4. le Runner accepte un job portant le bon libellé ;
  5. Xcode et le trousseau sont utilisables par ce job.

Un état « en ligne » dans l’interface ne prouve donc pas que le Mac peut publier une application.

SECTION 02Première étape : contrôler l’outil Xcode réellement appelé

La reproductibilité ne dépend pas seulement du nom de l’application installée. Elle dépend du chemin actif, de l’outil en ligne de commande, du SDK sélectionné, des dépendances résolues, du shell et du compte qui exécute la tâche.

Ajoutez d’abord un diagnostic sans secret dans le job ou dans un script de contrôle :

set -eu

xcode-select --print-path
xcodebuild -version
xcodebuild -showsdks
which xcodebuild
printf '%s\n' "$SHELL"

Ces commandes indiquent le chemin de développement actif, la version réellement invoquée et les SDK disponibles. Ne vous contentez pas de lister les applications dans /Applications : deux Macs peuvent contenir les mêmes versions de Xcode tout en utilisant des chemins actifs différents.

Verrouillez également les éléments qui influencent la compilation :

  • le fichier de dépendances et son fichier de verrouillage ;
  • la méthode de résolution des paquets Swift ;
  • la destination de compilation ;
  • le schéma partagé dans le dépôt ;
  • les réglages de signature prévus pour le CI ;
  • les variables de shell nécessaires, sans secret en clair.

La première preuve utile n’est pas un simple build de débogage. Lancez le même commit dans un projet isolé et distinguez trois résultats : résolution des dépendances réussie, compilation réussie et Archive réussie. Un projet peut compiler sans erreur tout en échouant au moment de produire une archive distribuable.

Apple décrit le flux officiel dans sa documentation sur l’Archive et la distribution des applications. Utilisez cette référence pour comparer la procédure manuelle à la sortie du pipeline.

Exemple minimal, à adapter avec des espaces réservés :

set -eu

PROJECT_PATH="CHEMIN_VERS_LE_PROJET.xcodeproj"
SCHEME_NAME="NOM_DU_SCHEME"
ARCHIVE_PATH="$CI_PROJECT_DIR/build/NOM_APPLICATION.xcarchive"

xcodebuild \
  -project "$PROJECT_PATH" \
  -scheme "$SCHEME_NAME" \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$ARCHIVE_PATH" \
  clean archive

Ne remplacez jamais ces espaces réservés par un identifiant d’équipe, une identité de certificat ou une valeur d’authentification dans un dépôt public.

Rappel d’exploitation : un changement de Xcode, de SDK ou de dépendance doit être traité comme une modification de l’environnement de construction. Conservez la sortie du diagnostic avec l’Archive correspondante afin de pouvoir expliquer une différence entre deux builds du même commit.

SECTION 03Deuxième étape : router les tâches sans contaminer le Mac de publication

Le routage doit empêcher un job Android, backend ou expérimental de partir par erreur sur le Mac qui possède les certificats de distribution. Dans GitLab, les étiquettes du Runner, les règles du projet et les branches protégées permettent d’associer une tâche à l’hôte prévu. Consultez les règles GitLab sur les étiquettes et les tâches protégées avant de définir votre politique.

Attribuez au Runner un libellé explicite, par exemple ios-release-macos, puis exigez ce libellé pour l’Archive et la publication :

archive_ios:
  tags:
    - ios-release-macos
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  script:
    - ./ci/check_xcode.sh
    - ./ci/archive_ios.sh

Le nom de branche et les scripts sont des exemples. La règle doit surtout empêcher une demande provenant d’une branche non approuvée de sélectionner le Runner de publication. Celui-ci ne devrait pas accepter de scripts arbitraires provenant de demandes de fusion non fiables, surtout si son compte peut lire une clé privée ou déverrouiller un trousseau.

Décidez aussi si les tâches de construction, de test et de publication partagent le même compte et le même répertoire. Le partage est simple, mais il augmente le risque de fichiers résiduels, de dépendances incohérentes et de secrets accessibles à une tâche qui n’en a pas besoin. Une séparation est préférable lorsque :

  • les tests proviennent de contributions externes ;
  • la publication utilise un certificat de distribution ;
  • plusieurs dépôts atteignent le même hôte ;
  • les tâches produisent des fichiers temporaires volumineux ;
  • un développeur doit récupérer un artefact sans accéder au trousseau.

Outil de décision : votre Mac peut-il devenir le Runner de publication ?

Cochez chaque condition avant d’affecter un vrai pipeline au Mac :

  • [ ] le compte du Runner est distinct du compte de travail quotidien ;
  • [ ] ce compte peut ouvrir sa session après un redémarrage ;
  • [ ] le LaunchAgent démarre le service sans intervention imprévue ;
  • [ ] l’étiquette du Runner n’est pas disponible pour les tâches non approuvées ;
  • [ ] la version active de Xcode est contrôlée par xcode-select et xcodebuild ;
  • [ ] le trousseau et la clé privée ne sont accessibles qu’aux jobs nécessaires ;
  • [ ] le répertoire de travail est nettoyé ou isolé entre deux projets ;
  • [ ] l’Archive, l’export et l’envoi vers TestFlight ont été testés ;
  • [ ] le même flux fonctionne après un redémarrage du Mac.

Appliquez ensuite cette décision :

  • Si toutes les cases sont cochées, choisissez ce Mac comme Runner de publication et surveillez les premiers pipelines depuis une branche protégée.
  • Si seules la session ou la sélection Xcode posent problème, utilisez-le pour un projet de validation, mais bloquez la publication jusqu’à correction.
  • Si l’isolation, les secrets ou l’accès aux scripts ne sont pas maîtrisés, revenez à un Runner distinct ou à un Mac distant réservé.
  • Si la reprise après redémarrage échoue, le Runner n’est pas prêt pour une utilisation permanente, même si une exécution manuelle vient de réussir.

Cette liste constitue un test d’acceptation : chaque case non cochée doit produire une action corrective et une nouvelle exécution contrôlée.

SECTION 04Troisième étape : séparer variables, certificats et profils

Une clé d’API App Store Connect ne remplace pas toute la chaîne de signature. Vous devez distinguer :

  • les variables CI/CD qui transmettent une valeur au job ;
  • la clé d’API utilisée pour certaines opérations App Store Connect ;
  • le certificat de distribution ;
  • la clé privée associée ;
  • le trousseau macOS contenant la clé privée ;
  • le Provisioning Profile qui autorise la combinaison entre application, équipe, capacités et mode de distribution.

La documentation GitLab sur les variables CI/CD décrit l’usage des variables protégées, masquées et de type fichier. Ces fonctions limitent l’exposition, mais elles ne rendent pas un script de pipeline sûr par défaut. Avec un Shell executor, le script possède le niveau d’accès accordé au compte macOS.

Conservez les certificats et profils hors du dépôt. Pour montrer la logique sans divulguer d’informations sensibles, utilisez uniquement des espaces réservés :

set -eu

CERTIFICATE_FILE="$CI_PROJECT_DIR/ci/PLACEHOLDER_CERTIFICATE.p12"
PROFILE_FILE="$CI_PROJECT_DIR/ci/PLACEHOLDER_PROFILE.mobileprovision"
KEYCHAIN_NAME="PLACEHOLDER_KEYCHAIN"
KEYCHAIN_PASSWORD="$PLACEHOLDER_KEYCHAIN_PASSWORD"

security import "$CERTIFICATE_FILE" \
  -k "$KEYCHAIN_NAME" \
  -P "$PLACEHOLDER_CERTIFICATE_PASSWORD" \
  -T /usr/bin/codesign

security unlock-keychain \
  -p "$KEYCHAIN_PASSWORD" \
  "$KEYCHAIN_NAME"

mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
cp "$PROFILE_FILE" \
  "$HOME/Library/MobileDevice/Provisioning Profiles/"

Ne publiez jamais un mot de passe réel, un jeton, un identifiant d’équipe, un identifiant de clé ou le contenu d’un certificat dans le dépôt, l’article ou les journaux. Supprimez les fichiers temporaires à la fin du job et vérifiez que les commandes ne les impriment pas.

Pour l’authentification de publication, utilisez une clé créée avec les permissions minimales compatibles avec votre opération. Apple explique la création des clés d’API App Store Connect. Cette clé sert à l’accès à l’interface ou à l’API selon l’outil utilisé ; elle ne remplace pas le certificat qui permet de produire un binaire distribuable.

SECTION 05Quatrième étape : distinguer cache, artefacts et publication

Le cache accélère la récupération ou la reconstruction de dépendances. Les artefacts servent à transmettre ou conserver la sortie d’un job. GitLab décrit cette différence dans sa documentation sur le cache et les artefacts. Cette séparation évite de placer un certificat, un trousseau exporté ou une Archive unique dans un cache réutilisable.

Concevez la clé de cache autour des fichiers de verrouillage. Si un fichier de dépendances change, la clé doit changer également ; sinon, un job peut reprendre un état qui ne correspond plus au commit. Le cache doit contenir des dépendances récupérables, pas une preuve de publication ni un secret.

Reliez explicitement les artefacts suivants :

  • l’xcarchive créé par le job d’Archive ;
  • le paquet exporté destiné à l’envoi ;
  • le fichier xcresult contenant les résultats de test et les diagnostics ;
  • les journaux permettant de rapprocher le commit, le schéma et l’outil Xcode.

N’appliquez pas la même conservation à ces fichiers. La durée et la capacité de conservation doivent être définies par votre projet ou vérifiées dans les réglages GitLab en vigueur. Un paquet de publication doit rester relié à son commit, à son Archive et à ses résultats de test ; sans cette relation, l’audit devient difficile même si TestFlight a accepté le build.

SECTION 06Cinquième étape : exécuter l’Archive, l’export et l’envoi

L’acceptation finale doit partir d’une branche protégée et suivre le flux réel. Un Debug Build ne remplace pas une Archive de distribution. Procédez dans cet ordre :

  1. récupérer un commit connu dans un projet isolé ;
  2. contrôler le chemin Xcode et les SDK ;
  3. résoudre les dépendances à partir des fichiers verrouillés ;
  4. exécuter les tests et conserver xcresult ;
  5. créer une Archive en configuration de distribution ;
  6. exporter le paquet avec la signature attendue ;
  7. authentifier l’envoi avec la méthode retenue ;
  8. transmettre le build à App Store Connect ;
  9. contrôler son état de traitement ;
  10. conserver les artefacts et journaux associés.

Apple documente les conditions d’envoi des builds vers App Store Connect. Un envoi accepté par l’outil ne signifie pas nécessairement que le build est immédiatement disponible dans TestFlight : le traitement côté service doit encore être vérifié.

Séparez donc le résultat « binaire envoyé » du résultat « traitement terminé ». Si votre automatisation dépend d’un build visible dans TestFlight, ajoutez un contrôle explicite avec l’outil autorisé et gérez clairement les erreurs sans exposer la clé d’API dans les logs.

SECTION 07Le Runner revient-il après un redémarrage du Mac ?

Cette épreuve révèle les installations qui fonctionnent seulement lorsqu’un développeur intervient manuellement. Redémarrez le Mac pendant une fenêtre de test et contrôlez :

  • la disponibilité de la session du compte CI ;
  • le démarrage du service utilisateur ;
  • la présence du Runner dans GitLab ;
  • l’association correcte de l’étiquette ;
  • le chemin renvoyé par xcode-select ;
  • l’accès au trousseau ;
  • la capacité à créer une nouvelle Archive et à envoyer le paquet.

Un Runner hors ligne après redémarrage doit être analysé en séparant le démarrage du service, la connexion de l’utilisateur et l’accès au trousseau. Réenregistrer le Runner à chaque incident masque souvent la cause et peut créer plusieurs agents concurrents.

Pour un Mac distant, ajoutez une vérification hors pipeline : accès VNC ou console, espace disque, état du compte CI et date de la dernière connexion. L’accès graphique ne remplace pas la surveillance GitLab, mais il peut être nécessaire pour récupérer une session bloquée ou traiter un dialogue système après une mise à jour.

SECTION 08Cas d’usage : passer d’un Mac personnel à un Runner dédié

Imaginez un indépendant qui développe une application audio et un outil de montage vidéo. Son code est déjà dans GitLab ; le Runner Linux exécute les tests de logique, mais la publication reste manuelle depuis son Mac personnel. Le premier échec survient lorsque le job iOS arrive sur Linux et ne trouve pas Xcode. Le second apparaît sur un Mac partagé : le Runner est en ligne, mais le compte de service ne retrouve pas la clé privée dans le trousseau.

La correction n’est pas d’ajouter une commande au hasard. Il faut créer un Runner macOS réservé, lui attribuer une étiquette de publication, vérifier la session, sélectionner l’outil Xcode, importer les éléments de signature avec des variables protégées, puis exécuter une vraie Archive. Les tâches de rendu audio ou vidéo peuvent alors rester séparées des fichiers et secrets de publication.

Utilisez cette classification après le test :

  • Validé : le Runner accepte uniquement les tâches prévues, l’Archive est signée, l’envoi est accepté, le build est traité et le pipeline fonctionne après redémarrage.
  • À corriger : un indicateur échoue, par exemple le trousseau ou la sélection Xcode. Bloquez l’envoi, corrigez l’environnement et recommencez.
  • Inadapté à un hôte partagé : des scripts non fiables peuvent atteindre les certificats, la session n’est pas stable ou les tâches se contaminent. Ne placez pas la distribution sur ce Mac.

SECTION 09Mac local ou Mac distant : quel choix pour votre pipeline ?

Un Mac local offre un accès physique immédiat, mais il peut être éteint, occupé par le développement, modifié avant un build ou indisponible pendant un déplacement. Il faut aussi réserver du stockage, surveiller les mises à jour et éviter de mélanger compte personnel et secrets de distribution.

Un Mac distant sépare mieux l’environnement de publication et facilite une disponibilité régulière, mais il dépend de l’accès distant, de la session utilisateur et de la procédure de récupération. Il convient moins à une équipe qui exige des interfaces physiques ou une charge lourde et permanente sans avoir comparé le coût d’un hôte administré en interne.

Appliquez cette décision :

  • Si votre Mac possède un compte CI séparé, un trousseau contrôlé, un stockage réservé et une reprise testée, vous pouvez conserver la chaîne locale.
  • S’il sert au développement quotidien, redémarre sans procédure documentée ou reçoit des scripts non fiables, ne lui confiez pas la publication.
  • Si vous avez besoin d’un environnement temporaire pour valider le pipeline, choisissez un Mac distant, répétez les six contrôles, puis sélectionnez une durée courte, mensuelle ou plus longue selon votre fréquence réelle.
  • Si la charge est stable, lourde et permanente, comparez l’achat et l’administration interne d’un Mac avec la location.
  • Si vous devez brancher des appareils physiques, gardez un Mac local pour ces opérations, même si le Runner de publication est distant.

Après avoir défini vos exigences de session, de stockage et de confidentialité, vous pouvez consulter les solutions de Mac distant de MACNOX. Pour démarrer le test d’un environnement réservé, la demande d’accès à un Mac distant MACNOX s’inscrit ensuite dans une démarche de validation, et non dans un remplacement de votre politique de secrets.

Votre solution actuelle peut avoir trois défauts concrets : un Runner Linux ne peut pas appeler Xcode, un Mac personnel mélange souvent session interactive et certificats, tandis qu’un Mac partagé offre une isolation limitée avec le Shell executor. Après une validation complète, louer un Mac auprès de MACNOX peut donc offrir une expérience plus propre pour un pipeline GitLab CI temporaire ou en phase de lancement, à condition d’appliquer les mêmes exigences de sécurité et de reprise qu’avec un hôte acheté.

Commencez par un dépôt de test, puis répétez l’acceptation depuis une branche protégée. Si le Mac redémarre, retrouve sa session, reprend les tâches, crée une Archive signée et permet de suivre le traitement dans TestFlight, vous avez validé un Runner de publication plutôt qu’un simple Runner enregistré.