Dans beaucoup d'equipes francophones, la soumission App Store ressemble encore a ceci : le PM relance sur Slack, le referent iOS archive manuellement dans Xcode, envoie TestFlight a minuit, puis ressaisit les metadonnees le lendemain. La fonctionnalite est mergee depuis longtemps. Ce qui bloque vraiment, c'est l'absence d'une chaine release industrialisee. Cet article adopte une vue CI/CD du parcours de Xcode a l'approbation : six etapes observables, frontieres claires entre signature, build, upload, soumission et publication, plus un runbook directement adaptable. Si vous hesitez encore sur l'emplacement du noeud de build, commencez par la logique de couches dans Nouvelles approches pour les pipelines iOS.
A la fin de la lecture, vous disposerez d'une carte en six etapes, d'un tableau manuel vs automatise, d'un YAML minimal fastlane + GitHub Actions, et d'une checklist pour republier sur TestFlight en 30 minutes apres un refus.
Pourquoi traiter la soumission comme une chaine d'ingenierie, pas comme une checklist
Les equipes debutantes voient souvent la mise en ligne comme une checklist : captures, politique de confidentialite, clic sur Soumettre. Le probleme : une checklist n'enregistre pas l'etat et ne garantit pas que la release N repose sur les memes hypotheses que la release 1. Sous l'angle ingenierie, la soumission est un pipeline oriente ou chaque etape produit des artefacts explicites et la suivante ne consomme que la sortie precedente :
- Reproductible : le meme tag git sur un ilot de build doit produire le meme IPA qu'en local (Xcode et verrous de dependances identiques)
- Observable : chaque etape a des logs, une duree et des codes d'echec ; Slack doit afficher « upload reussi / en traitement / pret pour revision », pas « je crois que c'est passe »
- Reversible : le store conserve le build precedent ; git conserve tags et dSYM pour ne pas casser la symbolisation des crashs
- Separation des roles : l'ingenierie gere binaires et numeros de version ; produit/ops gere textes et captures — la CI separe ces responsabilites par jobs ou portails d'approbation
Les equipes cross-plateforme sans Mac suivent la meme logique : coder sur Windows ou Linux, concentrer les etapes release sur un noeud macOS. Pour le modele de repartition, voir poste principal Windows/Linux + ilot Mac distant.
Six etapes : du scheme Xcode a la mise en vente sur l'App Store
Le tableau ci-dessous structure tout l'article. Le runbook plus loin ordonne les jobs dans cet ordre pour eviter d'uploader un build avant d'avoir incremente le numero de version.
| Etape | Actions principales | Duree typique | Niveau d'automatisation |
|---|---|---|---|
| ① Pre-vol | Numeros de version, verrous de dependances, tests unitaires, brouillon manifeste confidentialite | 5–15 min | Eleve (Linux ou Mac) |
| ② Preparation signature | Certificats, profils, deverrouillage trousseau, cle API ASC | Ponctuel + rotation | Moyen (injection de secrets) |
| ③ Archive & Export | xcodebuild archive, export IPA | 10–40 min | Eleve (Mac dedie) |
| ④ TestFlight | Upload, traitement, groupes internes | 15–60 min | Eleve |
| ⑤ Soumission | Metadonnees, captures, conformite export, submit for review | 30 min humain + attente | Moyen (portail d'approbation) |
| ⑥ Publication | Approbation, phased release / deploiement complet | 24–48 h revision | Moyen (suivi de statut) |
Le veritable seuil d'ingenierie se situe entre les etapes ④ et ⑤ : TestFlight doit etre le plus automatise possible (chaque merge sur main ou build nocturne) ; la soumission merite un portail humain ou PM pour eviter d'envoyer des textes incomplets en file de revision. L'etape ③ exige un Mac avec version Xcode figee — le cas d'usage principal d'un Cloud Mac comme noeud CI iOS, pas pour ecrire du Swift au quotidien.
Signature et certificats : la source de non-determinisme la plus couteuse en CI
L'archive fonctionne en local dans Xcode ; la CI echoue avec errSecInternalComponent ou No profiles for ... — presque toujours parce que l'identite et l'environnement ne sont pas figes, pas parce que le projet est casse.
Trois types de credentials — ne les melangez pas dans un seul secret
| Credential | Usage | Stockage CI recommande |
|---|---|---|
| Certificat Distribution (p12) | codesign des builds installables | Secret chiffre ; trousseau de l'ilot ou keychain temporaire par job |
| Profil de provisionnement | Lie bundle ID, capacites, appareils | Telecharge par UUID dans un repertoire prive au job ; pas d'ecrasement global |
| Cle API ASC (.p8) | Upload, statut build, soumission deliver | Secret GitHub chiffre ; pas d'import dans le trousseau |
Quand plusieurs jobs signent en parallele, la contention sur le trousseau et les courses sur les repertoires de profils sont des sources de pannes rares mais tenaces. Utilisateurs isoles par runner et keychain temporaire par job sont les solutions habituelles — details dans la FAQ codesign, trousseau et isolation des profils.
Script minimal de trousseau CI (Xcode 16 / macOS 14)
# 在 self-hosted Mac runner 上,每个 release job 开头
KEYCHAIN=$RUNNER_TEMP/build.keychain-db
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security list-keychains -s "$KEYCHAIN" login.keychain-db
security import dist.p12 -k "$KEYCHAIN" -P "$P12_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
Artefacts de build : archive → export → upload TestFlight
La difference entre build release et debug n'est pas « un flag d'optimisation de plus ». C'est que la configuration de signature, les options d'export et les metadonnees de version doivent correspondre a l'enregistrement App deja cree dans App Store Connect.
xcodebuild archive (equivalent ligne de commande de Product → Archive)
xcodebuild -scheme MyApp -configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
archive \
DEVELOPMENT_TEAM=XXXXXXXXXX \
-allowProvisioningUpdates
Export IPA et upload
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist ExportOptions.plist
# ExportOptions.plist 中 method 应为 app-store
# 上传可用 xcrun altool(旧)或 fastlane pilot / deliver
Points d'ingenierie essentiels :
- CFBundleVersion (build number) doit s'incrementer a chaque upload, lie au tag git ou au numero de run CI — documentez-le dans le runbook
- Conservez .xcarchive et dSYM dans GitHub Artifacts ou S3 ; refus et crashs production dependent des fichiers de symboles
- Les cles de cache DerivedData incluent la version majeure Xcode pour eviter des compiles avec cache sale qui passent le build mais echouent a la signature apres une montee de version
La strategie disque et cache de l'ilot de build impacte directement la stabilite de duree de l'etape ③ — voir la FAQ cache et disque des runners auto-heberges.
Soumission et revision : quoi automatiser au-dela de la revue humaine
La revision Apple reste majoritairement humaine, mais la plupart du travail repetitif avant et apres peut etre pipeline :
Bons candidats a l'automatisation
- Suivi du statut de traitement du build (
processing→valid) - Verification que les reponses enregistrees sur conformite export, droits de contenu et identifiant publicitaire restent valides
- fastlane
deliverou API App Store Connect pour soumettre metadonnees et captures (prerequis : sources des captures dans git) - Notification Slack apres approbation ou declenchement du pourcentage de phased release
Garder un portail humain pour
- Premiere mise en ligne dans une region, ou categories sensibles : paiements, sante, enfants
- Textes marketing de version majeure, video de preview et captures finales du store
- L'action « Soumettre pour revision » elle-meme — via GitHub Environment
required_reviewersouworkflow_dispatchmanuel
En cas de refus (Rejected) ou de refus de metadonnees (Metadata Rejected), le runbook doit imposer : corriger dans git, incrementer le build, relancer ③→④ — ne pas se contenter de modifier Connect dans le navigateur sans envoyer un nouveau binaire (sauf probleme purement metadata). Les causes frequentes — manifeste de confidentialite manquant, compte demo requis pour la connexion, declarations SDK tierces incompletes — doivent etre detectees a l'etape ① par un scan de PrivacyInfo.xcprivacy et des cles critiques de Info.plist.
Runbook CI/CD : boucle minimale GitHub Actions + fastlane
Voici le workflow minimal « merge main → TestFlight visible ». Le job de soumission est desactive par defaut ; declenchez-le avec un tag release/* ou un declenchement manuel.
Extrait Fastfile (upload TestFlight)
# fastlane/Fastfile
default_platform(:ios)
platform :ios do
lane :beta do
setup_ci if ENV['CI']
match(type: "appstore", readonly: true)
build_app(
scheme: "MyApp",
export_method: "app-store",
output_directory: "./build"
)
upload_to_testflight(
skip_waiting_for_build_processing: false,
api_key_path: "asc_api_key.json"
)
end
lane :release do
upload_to_app_store(
submit_for_review: true,
automatic_release: false,
precheck_include_in_app_purchases: false
)
end
end
GitHub Actions : pre-vol + release (Mac auto-heberge)
name: App Store Release
on:
push:
branches: [main]
workflow_dispatch:
inputs:
submit_review:
description: 'Submit to App Review'
type: boolean
default: false
jobs:
preflight:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: swiftlint --strict
- run: swift test --parallel
- name: Check Privacy Manifest exists
run: test -f MyApp/PrivacyInfo.xcprivacy
beta:
needs: preflight
runs-on: [self-hosted, macos, ios-release]
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: ~/ci/DerivedData
key: dd-xcode16-${{ hashFiles('**/Package.resolved') }}
- name: TestFlight upload
env:
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
run: bundle exec fastlane beta
submit-review:
if: github.event.inputs.submit_review == true
needs: beta
runs-on: ubuntu-latest
environment: app-store-review
steps:
- uses: actions/checkout@v4
- run: bundle exec fastlane release
Gardez GitHub Actions comme couche d'orchestration et figez l'execution sur un ilot Mac auto-heberge — meme philosophie que le pipeline hybride : Linux pour les checks rapides, Mac uniquement pour archive et upload. Pour une capacite burst en semaine de release, voir la section correspondante de cet article. Cote operationnel, les pages tarifs forfaits et configuration commande relient la decision technique a l'execution concrete.
Anti-patterns courants : quand ne pas automatiser entierement la soumission
L'industrialisation ne signifie pas confier chaque bouton a un robot. Forcer un submit_for_review entierement automatique se retourne souvent contre vous dans ces cas :
- Les textes du store vivent encore dans un Google Doc — Connect et le document divergent → Metadata Rejected
- L'API backend est fortement couplee a la version de l'app — confirmez que le deploiement serveur est pret avant de soumettre
- Premiere integration ATT, paiements ou HealthKit — les Q&R de revision et la video demo exigent souvent une validation produit en direct
- Signature sur des runners macOS heberges partages — la derive d'environnement est plus difficile a diagnostiquer que la file d'attente ; la chaine release appartient a des Mac dedies
La strategie par defaut la plus sure : tout automatiser jusqu'a TestFlight, semi-automatiser jusqu'a App Review. Les testeurs internes valident sur TestFlight ; le PM approuve dans l'Environment ; puis lancez le job submit-review.
FAQ
Faut-il un Mac local pour soumettre sur l'App Store ?
Pas obligatoirement. Le developpement quotidien peut se faire sur Windows ou Linux, mais l'archive, la signature et l'upload TestFlight exigent macOS. Les equipes sans Mac local peuvent executer toute la chaine via SSH ou VNC sur un Cloud Mac ou un ilot de build auto-heberge.
TestFlight et soumission App Store peuvent-ils partager le meme pipeline CI ?
Oui — partagez les etapes de build et de signature, mais separez en deux jobs : upload_to_testflight d'abord pour l'interne ; le job de soumission doit passer par une validation manuelle ou un portail d'approbation et verifier separement metadonnees, captures et manifeste de confidentialite.
fastlane ou xcodebuild natif : comment choisir ?
xcodebuild gere la compilation et l'archive ; fastlane encapsule signature, upload, metadonnees et soumission deliver. Les petites equipes peuvent tout confier a fastlane ; les grands depots appellent souvent xcodebuild explicitement en CI et reservent fastlane a la couche release.
Cle API ASC ou certificat p12 : lequel pour la CI ?
Preferez une cle API ASC (.p8) pour l'upload binaire et le suivi de statut de revision — pas d'import dans le trousseau. codesign exige toujours un certificat Distribution et un profil de provisionnement. Pratique courante : cle API pour Connect, p12 pour la signature, secrets separes.
Comment republier vite apres un refus de revision ?
Conservez le tag git et le dSYM du build refuse ; apres correction, incrementez le build number, relancez le meme pipeline release upload_to_testflight, validez sur TestFlight puis submit_for_review. Le build number doit croitre de facon monotone et la version des metadonnees rester coherente.
Export Compliance et manifeste de confidentialite : saisie manuelle obligatoire ?
La premiere fois, remplissez dans App Store Connect et enregistrez un modele ; la CI peut reecrire les reponses validees via fastlane deliver ou l'API App Store Connect. Si l'app n'utilise que HTTPS pour le chiffrement, la plupart des equipes choisissent l'exemption standard et la consignent dans Info.plist.
Combien de temps de l'archive a l'approbation ?
Build : 10 a 40 minutes selon la taille du projet ; upload et traitement : 15 a 60 minutes ; revision : 24 a 48 heures en moyenne, plus long les jours feries. L'objectif d'ingenierie est de compresser et prevoir tout sauf l'attente chez Apple.
Un Cloud Mac distant convient-il comme noeud de soumission ?
Oui. Un Mac physique dedie, version Xcode figee, trousseau persistant et DerivedData stable offrent une signature plus fiable et des deadlines de release plus tenables qu'un runner heberge partage ; utilisez VNC pour le premier TCC, puis SSH sans interface pour les pipelines.
Conclusion
La soumission App Store, vue CI/CD, est une chaine release en six etapes — pas un Archive ponctuel dans Xcode. En separant signature, build, TestFlight, soumission et publication, vous localisez les echecs a une etape precise au lieu de « la mise en ligne a encore echoue ».
- Automatisez pre-vol et TestFlight ; ajoutez un portail d'approbation avant la soumission
- Stockez les credentials de signature en trois compartiments ; isolez trousseau et profils pour les builds concurrents
- Liez le build number au tag git ; conservez dSYM et archives
- Executez les etapes ③④ sur un ilot Mac dedie ; l'etape ① sur Linux
- Apres un refus, incrementez le build via le meme pipeline — ne modifiez pas seulement l'interface Connect
Prochaine etape : ajoutez un workflow TestFlight-only dans votre depot, validez-le de bout en bout, puis ajoutez un job de soumission avec environment: app-store-review. Testez le noeud de build sur un Cloud Mac a la journee pendant une semaine avant de decider d'un Mac mini permanent.
Lectures associees
- GitHub Actions a bout de souffle ? Nouvelles approches pour les pipelines iOS (2026)
- Pool CI Mac entreprise : codesign, trousseau et isolation des profils
- CI iOS lente et files GitHub Actions ? Cloud Mac en pratique
- Runners Mac auto-heberges : cache et disque FAQ
- Poste principal Windows/Linux + ilot Mac de build
- CI Mac entreprise : SSH, VNC et multi-depots