Anatomie d'un git push : ce qui se passe entre votre commit et la production

Vous tapez git push paas main. Quelques secondes plus tard, votre app est en ligne. Pour beaucoup d'utilisateurs, c'est de la magie. C'est exactement ce qu'on veut : la magie cache la complexité, vous restez productif.

Mais cette magie a une mécanique précise. On va l'ouvrir et regarder ce qui se passe à chaque seconde, du moment où le push touche notre serveur au moment où votre URL répond avec la nouvelle version. L'objectif n'est pas d'expliquer toute notre architecture, mais de vous donner une compréhension intuitive de la chaîne, utile quand vous diagnostiquez un déploiement étrange.

Étape 1 — Detect (200 ms)

Votre push arrive sur notre service git. Le premier travail consiste à analyser le contenu du repo pour décider quoi faire. C'est l'étape detect.

On parcourt l'arbre des fichiers à la racine. La présence de certains fichiers déclenche un buildpack particulier :

  • requirements.txt ou pyproject.toml → buildpack Python
  • package.json → buildpack Node.js
  • Cargo.toml → buildpack Rust
  • Dockerfile → on bypass les buildpacks et on construit l'image directement

Ce qui prend du temps n'est pas la détection elle-même mais la lecture des fichiers de spécification de version. Si vous avez déclaré "engines": { "node": "20.x" }, on récupère la dernière version Node 20. Si la version est nouvelle pour nous, on déclenche un téléchargement, ce qui peut ajouter 1 à 3 secondes.

Étape 2 — Build (10 à 60 s)

Le build construit votre image OCI. C'est l'étape la plus variable en durée parce qu'elle dépend de votre code.

On utilise les Cloud Native Buildpacks, un standard ouvert qui découpe le build en couches. Chaque couche est cachée indépendamment. Si vous changez seulement le code de votre app sans toucher à package.json, on rebuilde uniquement la dernière couche : 5 à 10 secondes pour la majorité des cas.

Le cache de couches est partagé entre toutes vos releases. Sur une app moyenne, le hit rate dépasse 80 %. C'est pour ça que les builds successifs sont rapides.

Pendant le build, on injecte les variables d'environnement de build (différentes des variables runtime). Vous pouvez avoir besoin d'une API_DOC_KEY pour générer la doc au build sans qu'elle soit présente au runtime.

Étape 3 — Test et scan (5 à 20 s)

Si vous avez déclaré une commande de test dans paas.toml, elle est exécutée maintenant. Pas plus tard. Mieux vaut détecter une régression avant de déployer.

En parallèle, on scanne l'image construite contre la base de données CVE. Cette base est mise à jour quotidiennement à partir des sources publiques (NVD, GHSA, OSV). Si on trouve une CVE critique non patchée, le déploiement est bloqué. Vous recevez le rapport et vous décidez : patcher la dépendance, ou bypass explicitement (avec une trace dans l'audit).

Étape 4 — Sign (200 ms)

Étape souvent invisible, fondamentale. On signe l'image construite avec une clé ECDSA dérivée de votre tenant. La clé privée ne quitte jamais le service de signature, qui tourne dans un environnement isolé.

Pourquoi signer ? Parce qu'entre le moment où l'image est construite et le moment où elle est démarrée sur un nœud de calcul, il y a un transport et un stockage intermédiaires. Si quelqu'un (ou quelque chose) altère l'image dans cet intervalle, la signature ne correspondra plus, et le démarrage sera refusé. Une mesure simple qui élimine une classe entière d'attaques.

Étape 5 — Deploy (15 à 30 s)

Maintenant on déploie. C'est ici qu'arrive la stratégie de déploiement choisie : rolling, canary, ou blue-green. Par défaut, c'est rolling.

Disons que votre app a 4 pods. Le rolling fait :

  1. Démarrer un 5e pod avec la nouvelle release
  2. Attendre qu'il passe son healthcheck (typiquement 5-15 s)
  3. Une fois sain, supprimer un des anciens pods
  4. Démarrer un autre pod nouvelle release, attendre, supprimer un ancien
  5. Répéter jusqu'à 4 nouveaux pods et 0 ancien

Pendant tout ce temps, vos utilisateurs ne voient aucune interruption. Le load balancer route vers les pods sains uniquement.

Si pendant le rolling, un nouveau pod ne passe pas son healthcheck, on s'arrête. Les anciens pods restent. Vous gardez la version qui marche en prod, vous diagnostiquez la nouvelle.

Étape 6 — Route (1 à 3 s)

Dernière étape : exposer votre app sur Internet. Le service de routage ajoute votre app au load balancer L7 partagé. Si c'est la première fois que cette URL est servie, on provisionne aussi le certificat TLS (Let's Encrypt). Pour une URL existante, c'est juste une mise à jour de configuration.

HTTPS, HTTP/3, compression Brotli, headers de sécurité (HSTS, CSP par défaut), tout est configuré automatiquement. Vous n'avez rien à faire.

Le total : 31 à 110 secondes selon votre app

Voilà la chaîne complète. Pour une app simple sans test extensif, c'est typiquement 30 à 45 secondes. Pour une app Java avec migrations Postgres, ça peut monter à 90-110 secondes (la JVM warmup, c'est la JVM warmup).

Ce qui est intéressant n'est pas la durée brute. C'est que chaque étape peut échouer indépendamment, et que l'échec est local. Si la signature échoue, votre app ne démarre pas avec une image non signée. Si le scan CVE bloque, vous ne déployez pas une vulnérabilité critique. Si le rolling détecte un crash, l'ancienne release reste servie. À aucun moment vous n'avez d'état intermédiaire bizarre où une partie de la prod tourne sur la nouvelle version et une autre sur l'ancienne avec des données incohérentes.

La règle qu'on s'est imposée : le déploiement réussit complètement ou pas du tout. Le pire scénario, c'est que la nouvelle version ne sort pas, mais l'ancienne continue à servir vos utilisateurs.

La prochaine fois que vous tapez git push paas main, vous saurez exactement ce qui se passe entre Counting objects et l'URL qui répond. La magie reste agréable, mais elle est aussi entièrement compréhensible.

Pour aller plus loin

← Retour au blog
Partager LinkedIn Partager X