DevOps

Pipelines CI/CD

Le CI/CD transforme chaque modification en un artefact immuable et testé, et déploie cet artefact de la même manière à chaque fois. Un pipeline est simplement une séquence de jobs qui s'exécutent dès que le code évolue.

intermediate15 min readUpdated 16 sept. 2026
.github/workflows/ci.yml
yaml
# .github/workflows/ci.yml
name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test
Déclencheur
Push, pull request ou tag
Runner
Une machine propre et jetable
Artefact
Construit une fois, promu partout
Gates
Lint, typecheck et tests
Livraison
Release automatisée, approbation manuelle
Déploiement
Automatisé jusqu'à la production

Pourquoi c'est important

Ce qu'un pipeline vous apporte

Chaque modification est vérifiée

Chaque push exécute les mêmes étapes de build, lint, typecheck et tests dans un environnement propre, afin que le code défectueux soit détecté lors de la revue plutôt qu'après une release.

Un artefact, plusieurs environnements

Le pipeline construit l'image ou le bundle une seule fois et promeut ces mêmes octets à travers le staging et la production ; ainsi, ce que vous avez testé est exactement ce que vous déployez.

Les releases deviennent banales

Les déploiements sont automatisés, observables et réversibles. Faire un rollback signifie redéployer le tag précédent, et non reconstruire à partir d'une ancienne branche sous pression.

Le tableau complet

Les trois piliers du CI/CD

Un événement déclenche le travail, un runner jetable l'exécute, et un artefact immuable circule entre les environnements.

Le déclencheur

Réagir

Un push, une pull request ou un tag lance le pipeline. L'événement détermine quels jobs s'exécutent et quel environnement ils ciblent.

Le runner

Exécuter

Une machine jetable récupère le code et exécute chaque étape. Rien ne survit entre les jobs, ce qui rend le résultat reproductible.

L'artefact

Livrer

Le build produit une sortie immuable, comme une image de conteneur ou un bundle, qui est promue sans modification à travers chaque environnement.

HTML5 en un coup d'oeil

Les composants d'un pipeline

Déclencheurs

Les événements on push, pull_request ou tag décident quand un pipeline démarre.

Jobs et étapes

Un job s'exécute sur un runner ; ses étapes s'exécutent dans l'ordre et s'arrêtent dès la première erreur (fail fast).

Artefacts

Le résultat du build est stocké une fois et téléchargé par les jobs suivants.

Mise en cache

Les caches de dépendances et de build réduisent le temps d'exécution de plusieurs minutes.

Matrices

Exécutez le même job sur différentes versions de Node.js et différents systèmes d'exploitation.

Secrets

Les identifiants sont injectés au runtime et masqués dans les logs.

Flux

Le fonctionnement d'un pipeline à chaque push

La même structure s'applique que le pipeline fasse dix lignes ou cent, et qu'il déploie vers une VM ou un cluster Kubernetes.

  1. 1

    Déclenchement sur push ou pull request

    La forge envoie un événement avec le commit, la branche et l'auteur, et le pipeline sélectionne les jobs correspondants.

  2. 2

    Récupération du code

    Un runner neuf clone le commit exact, pour qu'aucun résidu d'une exécution précédente ne puisse polluer celle-ci.

  3. 3

    Restauration des caches

    Les caches de dépendances et de build sont restaurés via le hash du lockfile, transformant une installation à froid en installation à chaud.

  4. 4

    Installation des dépendances

    Une installation basée sur un lockfile fige l'arbre des dépendances pour que chaque exécution et chaque machine résolvent les mêmes versions.

  5. 5

    Lint, typecheck et tests

    Les vérifications rapides s'exécutent d'abord et font échouer le pipeline rapidement. Un gate rouge empêche la construction de l'artefact.

  6. 6

    Construction de l'artefact ou de l'image

    La sortie est construite une fois et téléchargée ou poussée vers un registre, taguée avec le commit pour être traçable.

  7. 7

    Déploiement sur la branche main

    Seuls les merges vers la branche protégée déclenchent le déploiement, généralement via un environnement avec ses propres secrets et approbations.

  8. 8

    Exécution d'un smoke check

    Une requête rapide sur la nouvelle release confirme qu'elle répond avant que le trafic ne soit totalement basculé.

  9. 9

    Rapport de statut

    Le commit reçoit un statut de succès ou d'échec, et un déploiement échoué est annulé ou laisse la version précédente active.

Le guide complet

Pipelines CI/CD: Tout ce que vous devez savoir

Ce que signifient réellement le CI et le CD

L’intégration continue (Continuous Integration) est la pratique consistant à fusionner fréquemment le travail de chaque développeur dans une branche partagée et à le vérifier automatiquement. Chaque fusion déclenche un build, un passage de lint et la suite de tests sur une machine dédiée, indépendante des postes de développement. L’objectif est de détecter les problèmes d’intégration en quelques minutes, tant que la modification est encore mineure, plutôt que lors d’une fusion pénible à la fin d’une longue branche.

La livraison continue (Continuous Delivery) étend ce concept : la branche principale est toujours dans un état prêt pour la mise en production, et le pipeline peut la déployer à tout moment. L’artéfact est construit et testé à chaque modification ; un humain décide alors du moment de la mise en production. Le déploiement continu (Continuous Deployment) supprime cette décision humaine, en envoyant chaque modification qui franchit les étapes de validation directement en production.

La seule et unique différence entre la livraison et le déploiement est l’étape d’approbation. Tout ce qui précède — le build, les tests, l’artéfact — est identique.

Les équipes prétendent souvent « faire du CI/CD » alors qu’elles ne font ni l’un ni l’autre. L’intégration continue exige que le build s’exécute sur une machine partagée et faisant foi, et pas seulement sur un ordinateur portable avant un commit. La livraison continue exige que l’artéfact produit par le pipeline soit celui qui est déployé, et non un élément reconstruit manuellement au moment de la mise en production. Si l’un de ces éléments manque, la boucle est ouverte et les bénéfices s’estompent.

L’anatomie d’un pipeline

Chaque pipeline, qu’il s’agisse d’un fichier GitHub Actions de dix lignes ou d’une configuration d’entreprise comprenant un millier de jobs, est construit à partir du même vocabulaire.

  • Trigger — l’événement qui déclenche l’exécution : un push, une pull request, un tag, une planification ou un déclenchement manuel.
  • Workflow — le fichier qui déclare les triggers et les jobs. Dans GitHub Actions, il se trouve dans .github/workflows/.
  • Job — une unité de travail qui s’exécute sur un seul runner. Les jobs s’exécutent en parallèle, sauf si vous déclarez des dépendances avec needs.
  • Step — une commande unique ou une action réutilisable à l’intérieur d’un job. Les steps s’exécutent dans l’ordre et le job s’arrête à la première erreur.
  • Runner — la machine qui exécute un job. Les runners hébergés sont jetables ; les runners auto-hébergés sont des machines que vous gérez.
  • Artifact — un fichier produit par un job et stocké pour des jobs ultérieurs ou pour des humains : un binaire, un bundle, un rapport de test.
  • Cache — un répertoire restauré entre les exécutions pour éviter de répéter des tâches coûteuses, comme l’installation de dépendances.
  • Environment — une cible nommée telle que staging ou production avec ses propres secrets, règles de protection et URL.
  • Secret — une valeur chiffrée injectée dans un job au moment de l’exécution et masquée dans les logs.
  • Status check — le résultat (succès ou échec) renvoyé au commit, que la protection de branche peut exiger.

Une fois ces termes maîtrisés, n’importe quel système de CI devient lisible. Jenkins, GitLab CI, CircleCI et GitHub Actions utilisent tous les mêmes concepts, même si les termes varient légèrement.

Un workflow GitHub Actions, ligne par ligne

Le workflow utile le plus simple consiste à récupérer le code, installer les dépendances et exécuter les tests.

name: ci

on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci
      - run: npm test

on déclare les déclencheurs. Un push vers main ainsi que toute pull request lancent une exécution. jobs.test s’exécute sur un runner ubuntu-latest vierge. uses lance une action réutilisable ; run exécute une commande shell. npm ci installe exactement ce qui est fixé dans le lockfile, ce qui rend l’exécution reproductible. cache: npm indique à l’action de configuration de mettre en cache le répertoire npm, avec une clé basée sur le lockfile.

Ajoutez concurrency pour annuler les exécutions obsolètes, ce qui permet d’économiser du temps et de l’argent sur les branches actives :

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

Lorsqu’un développeur pousse trois commits d’affilée, seule l’exécution la plus récente subsiste. Les plus anciennes sont annulées, et la pull request affiche un seul statut actuel au lieu d’une file d’attente de statuts périmés.

Build once, déployez le même artefact

L’habitude la plus précieuse dans un pipeline est de compiler exactement une seule fois. Le pipeline compile, teste et package l’application une seule fois, et chaque environnement, du staging à la production, exécute ce même résultat.

L’alternative — recompiler pour chaque environnement — semble anodine, mais elle ne l’est pas. Différents builds peuvent résoudre différentes versions de dépendances, récupérer différents digests d’images de base ou intégrer un NODE_ENV différent. Le staging teste alors un binaire que la production n’exécutera jamais, et le fait que « ça a passé le staging » ne prouve rien.

Deux règles rendent le « build-once » pratique. Premièrement, taguez l’artefact par commit, jamais avec un nom mutable comme latest :

docker build -t ghcr.io/me/app:$GIT_SHA .
docker push ghcr.io/me/app:$GIT_SHA

Deuxièmement, injectez la configuration au runtime. L’image ne doit pas savoir si elle s’exécute en staging ou en production. Les URLs de base de données, les feature flags et les clés API arrivent sous forme de variables d’environnement lors du démarrage du conteneur. Le build est identique partout ; seul l’environnement diffère.

Pour promouvoir un build, référencez le digest immuable plutôt que le tag, afin qu’une image retaguée ne puisse pas dériver :

kubectl set image deploy/app \
  app=ghcr.io/me/app@sha256:...

C’est le fondement qui rend les rollbacks triviaux. La version précédente est simplement le digest précédent, toujours présent dans le registry, prêt à être déployé en quelques secondes.

Tests, lint et typecheck comme “gates”

Une “gate” (porte de validation) est un contrôle qui doit être réussi avant que le pipeline ne puisse continuer. Les gates essentielles sont rapides et peu coûteuses : le linting, le type checking et la suite de tests unitaires. Elles s’exécutent à chaque pull request et empêchent la construction de l’artéfact en cas d’échec.

Organisez-les de la plus rapide à la plus lente. Le lint et le typecheck se terminent généralement en quelques secondes et permettent de détecter une large catégorie d’erreurs. Viennent ensuite les tests unitaires. Les tests d’intégration et end-to-end, plus lents, peuvent être exécutés en parallèle ou uniquement sur la branche principale.

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22, cache: npm }
      - run: npm ci
      - run: npm test

Deux jobs s’exécutent en parallèle, chacun sur son propre runner. Le lint échoue en vingt secondes sans attendre la suite de tests. Le compromis est que les deux jobs installent les dépendances ; un cache partagé permet de limiter ce coût.

Les gates n’ont d’utilité que si elles sont obligatoires. Sur votre forge, marquez les contrôles comme requis dans la protection des branches afin qu’une pull request ne puisse pas être fusionnée tant que l’un d’entre eux est au rouge.

Matrices et parallélisme

Une matrice exécute une définition de job sur plusieurs combinaisons d’entrées. C’est ainsi que vous testez plusieurs versions de Node et différents systèmes d’exploitation sans dupliquer votre YAML.

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, macos-latest]
        node: [20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
          cache: npm
      - run: npm ci
      - run: npm test

Ceci se développe en quatre jobs. fail-fast: false permet aux autres de se terminer même si l’un d’eux échoue, ce qui est utile pour savoir si l’échec est spécifique à une version.

Le parallélisme n’est pas gratuit. Chaque job provisionne un runner et installe les dépendances ; ainsi, une matrice large coûte plus de temps et d’argent qu’une matrice restreinte. Testez les versions que vous supportez réellement, et non toutes les versions existantes.

Pour les suites de tests volumineuses, fragmentez (shard) les tests eux-mêmes : passez un index de shard et le total au test runner afin que chacun des quatre jobs exécute un quart des tests. La suite se termine alors en environ un quart du temps.

Secrets et variables d’environnement

Les secrets sont des valeurs chiffrées que le pipeline injecte au moment de l’exécution. Ils doivent être stockés dans le gestionnaire de secrets de la plateforme, jamais dans le dépôt, et jamais dans l’image.

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: ./deploy.sh
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
          API_KEY: ${{ secrets.API_KEY }}

La plateforme masque les valeurs de secrets connues dans les logs, mais ce masquage est fourni au mieux (« best-effort »). Il ne peut pas détecter un secret qui a été transformé, encodé en base64 ou affiché par un outil qui le reformate. La règle de sécurité est simple : n’affichez jamais un secret. N’exécutez pas env dans une étape de debug, ne faites pas de echo d’un token, et ne passez pas de secret à une commande qui logue ses arguments.

Limitez la portée des secrets à l’environnement qui en a besoin. Un déploiement de staging n’a aucune raison de posséder le mot de passe de la base de données de production. Sur GitHub, les secrets d’environnement ne sont disponibles que pour les jobs qui déclarent cet environnement, et peuvent nécessiter une approbation préalable.

Pour les fournisseurs de cloud, privilégiez l’OIDC aux clés à longue durée de vie. Le pipeline échange un token d’identité éphémère contre des identifiants temporaires, il n’y a donc aucun secret statique pouvant fuiter ou nécessitant une rotation.

permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/deploy
      aws-region: eu-west-1

Le token est valide quelques minutes et limité à un seul rôle. S’il fuite, le rayon d’impact est limité à une seule exécution.

Un avertissement mérite d’être souligné : les pull requests provenant de forks exécutent du code non approuvé. N’exposez jamais de secrets à ce code. Utilisez pull_request plutôt que pull_request_target pour tout ce qui récupère et build le code d’un contributeur, et exigez une approbation avant d’exécuter des workflows provenant de contributeurs pour la première fois.

Environnements, approbations et branches protégées

Un environnement est une cible de déploiement nommée possédant ses propres secrets, règles de protection et URL. C’est le mécanisme qui permet de distinguer la production de tout le reste.

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://app.example.com
    steps:
      - run: ./deploy.sh

Les environnements peuvent exiger une approbation manuelle avant l’exécution d’un job, restreindre les branches autorisées à déployer vers eux, et limiter le temps d’attente d’un déploiement avant l’expiration du délai (timeout). C’est ici que se trouve le « bouton humain » de la livraison continue : le pipeline s’exécute automatiquement jusqu’à la barrière, puis un réviseur le libère.

Les branches protégées complètent les environnements. Sur la branche main, exigez une pull request, la réussite des status checks, une revue, et interdisez les force pushes. Ensemble, ils garantissent que le seul moyen d’accéder à la production est de passer par le pipeline, ce qui est précisément l’objectif.

Stratégies de déploiement et rollbacks

La manière dont une nouvelle version remplace l’ancienne est une décision de conception qui a des conséquences réelles pour les utilisateurs.

Le Rolling deployment remplace les instances progressivement, quelques-unes à la fois. La capacité diminue légèrement pendant l’échange, mais aucune infrastructure supplémentaire n’est requise. C’est le mode par défaut sur la plupart des plateformes et le point de départ idéal.

Le Blue-green deployment fait fonctionner deux environnements complets. Le trafic est dirigé vers l’environnement “blue” pendant que le “green” reçoit la nouvelle version ; une fois que le “green” est opérationnel, un simple changement de routage bascule tout le trafic. Le rollback consiste à revenir en arrière, ce qui est quasi instantané. L’inconvénient est le coût lié à l’exécution de deux environnements simultanément.

Le Canary deployment envoie une petite fraction du trafic — un pour cent, puis cinq, puis cinquante — vers la nouvelle version, et surveille les taux d’erreur et la latence avant de poursuivre. Cela permet de détecter des problèmes qu’un health check ne pourrait pas identifier, au prix d’un routage et d’un monitoring plus complexes.

Quelle que soit la stratégie, le déploiement doit être réversible. Comme l’artefact précédent est immuable et toujours présent dans le registry, un rollback est un redéploiement du digest précédent, et non une reconstruction :

kubectl rollout undo deploy/app

Deux détails rendent les rollbacks sécurisés. Les migrations de base de données doivent être rétrocompatibles pendant au moins une version, afin que l’ancien code puisse toujours fonctionner avec le nouveau schéma. De plus, les feature flags vous permettent de désactiver un nouveau comportement sans rien déployer du tout, ce qui constitue le rollback le plus rapide qui soit.

Registres d’artefacts et images de conteneurs

Un registre d’artefacts stocke le résultat du build : images de conteneurs, packages npm, binaires, tarballs. C’est le point de passage entre le build et le déploiement, et il doit être immuable.

Les images de conteneurs sont les artefacts les plus courants car elles embarquent leur propre runtime. Un registre tel que GitHub Container Registry, Amazon ECR ou Docker Hub stocke des couches (layers), et le pipeline y effectue un push à chaque build. Les déploiements récupèrent ensuite le digest exact.

- uses: docker/build-push-action@v6
  with:
    context: .
    push: true
    tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
    cache-from: type=gha
    cache-to: type=gha,mode=max

Les options cache-from et cache-to permettent de persister le cache des couches Docker entre les exécutions, afin qu’une couche de dépendances inchangée ne soit pas reconstruite entièrement à chaque fois.

Mettez en place une politique de rétention. Les registres accumulent rapidement des gigaoctets de données, et la suppression des anciennes images est essentielle pour maintenir la rapidité du pipeline. Conservez au moins les dernières versions publiées pour permettre un rollback si nécessaire.

Vérifications de statut et protection de branche

Une vérification de statut (status check) est le verdict du pipeline sur un commit. La forge l’associe à la pull request, et la protection de branche détermine si elle est indicative ou obligatoire.

Configurez la branche main pour exiger :

  • Une pull request avant la fusion, avec au moins une approbation.
  • Des vérifications de statut réussies, incluant le lint, le typecheck et les tests.
  • Une branche à jour avec main avant la fusion, afin que les vérifications soient exécutées sur le code qui sera réellement déployé.
  • Aucun force push et aucune suppression.

Le résultat est que la branche main est toujours « verte ». Chaque commit qu’elle contient a franchi les mêmes étapes de validation, et chaque déploiement à partir de celle-ci repose sur un artefact dont la fiabilité est connue.

Gardez la liste des exigences courte. Imposer une suite de tests end-to-end lente sur chaque pull request fait attendre les développeurs ; l’exiger dans la file d’attente de fusion (merge queue) ou sur la branche main offre la même sécurité sans créer de frictions.

Mise en cache et builds incrémentaux

La mise en cache fait la différence entre un pipeline de deux minutes et un de douze minutes. Le principe est simple : ne jamais répéter un travail dont les entrées n’ont pas changé.

Le cache le plus précieux concerne les dépendances. Utilisez le hash du lockfile comme clé afin qu’il soit invalidé précisément lorsque les dépendances changent :

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ hashFiles('**/package-lock.json') }}
    restore-keys: npm-

restore-keys permet une correspondance partielle : même en cas d’absence de la clé exacte, le cache le plus récent est restauré et mis à jour de manière incrémentale, ce qui est bien plus rapide qu’une installation à froid.

Les outils de build ajoutent leurs propres caches. Le build incrémental de TypeScript, le pré-bundling des dépendances de Vite et le cache de couches de Docker bénéficient tous d’une persistance entre les exécutions. Pour Docker, utilisez le registre ou le backend de cache du fournisseur de CI plutôt que le démon local, qui est supprimé avec le runner.

Un cache est une optimisation, jamais une source de vérité. Si un cache est corrompu ou obsolète, l’exécution doit tout de même rester correcte. Les outils de build qui font aveuglément confiance à un cache peuvent produire des résultats erronés ; définissez donc vos clés de cache avec prudence et considérez l’absence de cache (cache miss) comme un comportement normal.

Pipelines pour les monorepos

Un monorepo regroupe plusieurs packages dans un seul dépôt. Un pipeline naïf reconstruit et reteste l’ensemble des packages à chaque modification, ce qui ralentit le processus à mesure que le dépôt s’agrandit.

La solution réside dans la détection des changements. Les filtres de chemin (path filters) et les graphes de dépendances indiquent au pipeline quels packages sont affectés par un diff, et seuls ceux-ci sont alors buildés.

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pnpm install --frozen-lockfile
      - run: pnpm turbo run test --filter='...[origin/main]'

Le filtre sélectionne les packages modifiés depuis main ainsi que tout ce qui en dépend. Une modification d’un utilitaire partagé déclenche le build de ses dépendants ; une modification d’un package “feuille” ne déclenche que son propre build.

Ajoutez un cache distant (remote cache) afin que les fichiers compilés et les résultats des tests soient partagés entre les machines et les exécutions. Si les entrées d’un package n’ont pas changé, son build est restauré plutôt que recalculé. Dans un monorepo, le gain est souvent plus important qu’avec le cache des dépendances.

Gardez votre pipeline rigoureux : une modification du lockfile à la racine ou d’une configuration partagée doit toujours déclencher l’exécution de la suite complète. Les tests sélectifs sont une optimisation qui ne doit jamais laisser passer un package défectueux.

Sécurité du pipeline

Le pipeline détient les identifiants de production, ce qui en fait une cible de choix. Traitez les fichiers de workflow comme du code sensible et examinez attentivement chaque modification.

Épinglez les actions à une version. Un tag comme @v4 est un pointeur mobile ; un SHA de commit complet est immuable. L’épinglage à un SHA est l’option la plus stricte et empêche un tag compromis d’exécuter du code malveillant dans votre pipeline.

Accordez le moindre privilège. Définissez explicitement permissions sur chaque workflow et job, en privilégiant la lecture seule par défaut, et n’ajoutez des permissions d’écriture que là où elles sont nécessaires.

permissions:
  contents: read

Le GITHUB_TOKEN par défaut est souvent plus large que ce dont un job a réellement besoin. Le restreindre permet d’éviter qu’une étape compromise ne puisse pousser des commits ou publier des packages.

Privilégiez l’OIDC aux clés statiques, comme décrit précédemment. Des identifiants à courte durée de vie limités à un seul rôle éliminent les secrets permanents que les attaquants recherchent le plus.

N’exécutez pas de code non fiable avec des secrets. Les pull requests provenant de forks sont le vecteur d’attaque classique. Utilisez pull_request pour le build et les tests, exigez une approbation pour les nouveaux contributeurs, et ne récupérez jamais et n’exécutez jamais le code d’un fork dans un workflow ayant accès à des secrets.

Examinez les actions tierces. Une action est une dépendance qui s’exécute avec vos identifiants. Privilégiez les actions provenant de la forge elle-même ou d’éditeurs reconnus, et auditez toutes les autres.

Bonnes pratiques

  • Générez l’artéfact une seule fois et faites-le progresser à travers chaque environnement.
  • Marquez les artéfacts par le SHA du commit ou par digest, jamais uniquement par latest.
  • Injectez la configuration au runtime ; gardez le build identique partout.
  • Exécutez le lint et le typecheck avant la suite de tests (plus lente) pour obtenir des retours d’échec rapides.
  • Rendez les vérifications importantes obligatoires dans la protection des branches.
  • Mettez en cache les dépendances en utilisant le hash du lockfile comme clé, avec des restore keys.
  • Limitez la portée des secrets aux environnements et privilégiez les identifiants OIDC à courte durée de vie.
  • N’affichez jamais les secrets et ne les exposez jamais à des pull requests non approuvées.
  • Épinglez les actions tierces et configurez les permissions de token selon le principe du moindre privilège.
  • Utilisez concurrency pour annuler les exécutions obsolètes.
  • Gardez le pipeline rapide ; un pipeline lent finit par être contourné.
  • Effectuez les rollbacks via un redéploiement du digest précédent.

Erreurs courantes

  • Reconstruire l’image différemment pour chaque environnement et appeler cela une « promotion ».
  • Intégrer la configuration de l’environnement dans l’image lors de l’étape de build.
  • Utiliser latest comme seul tag et perdre ainsi toute traçabilité.
  • Afficher des secrets dans une étape de debug en supposant que le masquage automatique capture tout.
  • Exposer des secrets aux workflows déclenchés par des pull requests provenant de forks.
  • Référencer des actions tierces via une branche instable.
  • Laisser les permissions du token par défaut trop permissives.
  • Marquer chaque check optionnel comme obligatoire, ralentissant ainsi considérablement les merges.
  • Laisser des tests instables (flaky tests) en échec jusqu’à ce que plus personne ne consulte les résultats.
  • Mettre en place un cache agressif sans clé d’invalidation basée sur les changements réels des entrées.
  • Tout reconstruire dans un monorepo à chaque modification.
  • Ne disposer d’aucun moyen de rollback autre que le revert d’un commit et l’attente du déploiement.

Et après ?

Un pipeline se termine par le déploiement d’un artefact ; la suite logique est donc d’explorer les Cloud Platforms, là où s’effectuent concrètement la mise en production, les health checks et les rollbacks. L’artefact est généralement un conteneur, et le guide Docker détaille les images et les builds multi-étapes produits par le pipeline. La plupart des runners étant des machines Linux, le guide Linux explique le shell et les outils dont dépendent vos étapes, tandis que le guide Node.js pose les bases du runtime et de la toolchain utilisée.

En pratique

Quatre jobs, un pipeline

Intégration continue, matrice de tests, build d'image et déploiement avec gate — la structure complète d'un pipeline de production.

.github/workflows/ci.yml
name: ci

on:
  push:
    branches: [main]
  pull_request:

concurrency:
  group: ci-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm

      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm test

Construire une fois, promouvoir l'artefact

Reconstruire par environnement signifie que le staging et la production exécutent des binaires différents construits à partir de la même source. Construisez une fois, taguez par commit, et promevez cet artefact exact.

Préférer
# build once, tagged by the commit
docker build -t ghcr.io/me/app:$GIT_SHA .
docker push ghcr.io/me/app:$GIT_SHA

# every environment runs the same digest
kubectl set image deploy/app \
  app=ghcr.io/me/app@sha256:...
Éviter
# rebuild separately for each environment
docker build -t app:staging \
  --build-arg NODE_ENV=staging .
docker build -t app:prod \
  --build-arg NODE_ENV=production .

# staging and prod are different binaries

Versions d'actions épinglées

Une action référencée par une branche mouvante peut changer à tout moment, ce qui représente un risque pour la fiabilité et la chaîne d'approvisionnement (supply-chain).

Préférer
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: docker/build-push-action@v6
Éviter
- uses: actions/checkout@master
- uses: some-org/some-action@main
# runs whatever the branch points at today

Compromis

Un pipeline complet vaut-il la mise en place ?

Un pipeline est du code que vous maintenez. Pour tout projet ayant plus d'un contributeur, il est rentabilisé presque immédiatement.

Strengths

  • Le feedback arrive tôt

    Un build cassé est un correctif de cinq minutes sur une pull request, pas un rollback à minuit. Plus un défaut est détecté tôt, moins il coûte cher.

  • Les releases sont répétables

    Les mêmes étapes s'exécutent à chaque fois, donc le déploiement ne dépend plus de qui est au clavier ni de l'état local de sa machine.

  • L'artefact est traçable

    Un commit correspond à un digest d'image, qui correspond à un déploiement actif. Répondre à « qu'est-ce qui est en production » devient une simple recherche, pas une enquête.

Trade-offs

  • Les pipelines lents sont ignorés

    Dès qu'une exécution prend trente minutes, les gens cessent de la surveiller et commencent à merger malgré les erreurs. Gardez les gates rapides et exécutez les tâches coûteuses en parallèle.

  • Les secrets sont une surface d'attaque

    Tout workflow exécutant du code non approuvé peut être manipulé pour fuiter des identifiants. Limitez strictement la portée des tokens, utilisez des identifiants OIDC éphémères et n'exposez jamais de secrets aux pull requests provenant de forks.

  • Les tests instables (flaky) brisent la confiance

    Un test qui échoue une fois sur dix apprend à l'équipe à cliquer sur « retry » plutôt qu'à analyser l'échec. Mettez les tests instables en quarantaine au lieu de les tolérer.

FAQ

Foire aux questions

Keep learning

Related topics from the roadmap.

$ commencer à apprendre

Prêt à apprendre CI/CD Pipelines ?

Notre tutoriel interactif vous guide à travers CI/CD Pipelines pas à pas — avec des quiz et du vrai code que vous pouvez exécuter dans le navigateur.