Changer le thème

Pipeline CI GitHub Actions : construire build et tests automatisés de zéro

Easton editorial illustration: solo-founder business system console

Le téléphone vibre. Un collègue écrit : « La prod est tombée, le code que tu as mergé hier pose problème. »

La tête tourne. Pourtant, les tests passaient en local. En fouillant les logs, on découvre que le poste local tourne en Node 20 alors que l’environnement de test est en Node 18 — un bug lié à un comportement d’API différent. À ce moment-là, on aurait aimé qu’un pipeline CI lance les tests avant le merge.

Lancer les tests à la main, neuf développeurs sur dix l’oublient. Le dixième, c’est souvent celui qui a déjà pris une claque. GitHub Actions règle ça : après un push, build, tests et déploiement tournent dans le cloud. Pas besoin d’y penser, la machine s’en charge.

Cet article vous guide pour construire de zéro un pipeline CI complet : un modèle de workflow prêt à copier, la stratégie Matrix pour des tests multi-versions en parallèle (plus de la moitié du temps de build en moins), plus les pièges et retours d’expérience. Prêt ? C’est parti.

Chapitre 1 : Prise en main rapide de GitHub Actions

Qu’est-ce que GitHub Actions

En bref, GitHub Actions est la plateforme d’automatisation intégrée à GitHub. Vous poussez du code en local, elle exécute tests, build et déploiement dans le cloud — en automatique.

Avant, le CI/CD passait souvent par un serveur Jenkins à installer, configurer et maintenir. Avec GitHub Actions, pas de serveur à gérer : un fichier YAML dans le dépôt suffit. Et vous disposez de 2 000 minutes gratuites par mois (illimité sur les dépôts publics), largement suffisant pour les projets perso et les petites équipes.

Par rapport à Jenkins ou Travis CI, les atouts sont nets : intégration profonde au dépôt GitHub, statut de build visible dans les PR ; configuration simple, sans Groovy ; écosystème riche avec des milliers d’Actions sur le Marketplace officiel. Les limites existent aussi : dépendance à GitHub, migration vers GitLab implique de réécrire la config ; pour des pipelines enterprise très complexes, Jenkins reste parfois plus souple. Pour la majorité des projets, GitHub Actions suffit.

Concepts clés en deux minutes

Au début, Workflow, Job, Step et Runner peuvent sembler confus. Voici une explication directe :

Workflow : un fichier YAML qui décrit tout un flux d’automatisation. Par exemple « à chaque push sur main, lancer les tests ». Il vit dans .github/workflows/.

Job : un ensemble d’étapes dans le workflow. Plusieurs jobs peuvent tourner en parallèle ou avec des dépendances. Par exemple un job « test », puis un job « deploy ».

Step : une action concrète dans un job, exécutée dans l’ordre. Ça peut être une commande (npm test) ou une Action réutilisable (actions/checkout@v4).

Runner : la machine virtuelle qui exécute le job. GitHub propose ubuntu-latest (Linux), windows-latest (Windows) et macos-latest (macOS). Vous pouvez aussi utiliser vos propres serveurs ; dans la plupart des cas, les runners officiels suffisent.

Image simple : le Workflow est le scénario, le Job une scène, le Step un geste dans la scène, le Runner l’acteur.

Votre premier workflow CI

Ne compliquez pas : faites tourner quelque chose. À la racine du projet, créez .github/workflows/ci.yml et collez-y ce code :

name: CI Pipeline  # nom affiché dans l'onglet Actions

on:
  push:
    branches: [main]  # déclenché au push sur main
  pull_request:
    branches: [main]  # déclenché pour les PR vers main

permissions:
  contents: read  # moindre privilège : lecture seule

jobs:
  build:
    runs-on: ubuntu-latest  # environnement Ubuntu récent
    timeout-minutes: 15     # timeout anti-blocage

    steps:
      - name: Checkout code
        uses: actions/checkout@v4  # récupère le code

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20   # Node.js 20
          cache: 'npm'        # cache npm activé

      - name: Install dependencies
        run: npm ci          # install fiable via lockfile

      - name: Run tests
        run: npm test        # exécute les tests

      - name: Build
        run: npm run build   # build

Que fait ce fichier ?

La section on définit les déclencheurs : push sur main ou PR vers main. permissions déclare les droits selon le principe du moindre privilège — ici contents: read uniquement, pour éviter qu’un workflow modifie le dépôt par accident. Le cœur : un job build sur Ubuntu qui enchaîne checkout, installation de Node, dépendances, tests et build.

Commitez, poussez sur GitHub, ouvrez l’onglet Actions du dépôt. Un cercle vert tourne : le runner exécute votre workflow. Après quelques minutes, si tout est vert, votre premier pipeline CI est en service.

Croix rouge ? Ouvrez les logs : chaque étape est détaillée. Dans 90 % des cas, c’est l’installation des dépendances ou les tests eux-mêmes qui échouent, pas la config CI.

Chapitre 2 : Configuration cœur du pipeline CI

Le workflow du chapitre 1 tourne, mais il n’est pas encore « production-ready ». Ce chapitre couvre quatre piliers : déclencheurs, permissions, variables d’environnement et cache des dépendances. Bien réglés, le workflow est plus sûr et plus rapide.

Déclencheurs : quand exécuter

Les déclencheurs fixent le moment où le workflow démarre. Les plus courants : push et pull_request.

on:
  push:
    branches: [main, dev]    # push sur main ou dev
    paths:
      - 'src/**'             # uniquement si src/ change
      - 'package.json'       # ou si les dépendances changent
  pull_request:
    branches: [main]         # PR ciblant main

Le filtre paths est très utile. Si le projet a un dossier docs/, modifier la doc ne devrait pas lancer le CI. Avec paths, seuls les changements de code déclenchent le build — moins de ressources, moins d’attente.

Autres déclencheurs possibles :

schedule : tâche planifiée en cron. Par exemple un build chaque nuit :

on:
  schedule:
    - cron: '0 0 * * *'  # chaque jour à minuit UTC

Sur un projet, je l’utilise pour vérifier les dépendances obsolètes chaque jour avec npm outdated et recevoir un rappel par e-mail.

workflow_dispatch : déclenchement manuel. Utile pour tester une config sans push. L’onglet Actions affiche un bouton « Run workflow ».

on:
  workflow_dispatch:  # déclenchement manuel

Gestion des permissions : la sécurité d’abord

Par défaut, GitHub Actions fournit un GITHUB_TOKEN capable de lire/écrire le dépôt, créer des PR, voire pousser du code. Pratique, mais risqué : un workflow compromis peut donner des droits d’écriture sur le dépôt.

En 2021, un incident de sécurité a montré qu’un workflow CI pouvait être détourné via une PR forgée. Depuis, GitHub recommande : déclarer explicitement le minimum de permissions.

permissions:
  contents: read    # lecture du dépôt uniquement
  pull-requests: write  # écriture PR si nécessaire

Pour un CI pur (tests + build), contents: read suffit. Release, commentaires sur PR, etc. : ajoutez les permissions au cas par cas.

Astuce : dans les paramètres du dépôt, passez la permission par défaut en « Read repository contents ». Tous les workflows n’ont alors que la lecture ; l’écriture se déclare explicitement. Une barrière de plus.

Variables d’environnement : gestion par niveaux

Trois niveaux : workflow, job, step. Plus le niveau est bas, plus la portée est étroite, avec possibilité d’écraser la valeur du niveau supérieur.

env:
  NODE_ENV: production     # niveau workflow, tous les jobs
  CI: true                 # outils CI détectent souvent cette variable

jobs:
  build:
    env:
      BUILD_TARGET: web    # niveau job, job build uniquement

    steps:
      - name: Run custom script
        env:
          MY_VAR: hello    # niveau step, cette étape seulement
        run: echo $MY_VAR

Pourquoi plusieurs niveaux ? Exemple : jobs build et deploy. NODE_ENV sert aux deux → niveau workflow. BUILD_TARGET ne concerne que build → niveau job. Un paramètre ponctuel pour un script → niveau step.

Données sensibles : ne les mettez jamais en clair dans le YAML. Utilisez Secrets : ajoutez la clé dans les paramètres du dépôt (ex. API_KEY), référencez-la avec ${{ secrets.API_KEY }}. Les Secrets sont masqués dans les logs.

steps:
  - name: Deploy to server
    env:
      SSH_KEY: ${{ secrets.SSH_KEY }}  # depuis Secrets
    run: |
      echo "$SSH_KEY" > private.key
      ssh -i private.key user@server 'deploy.sh'

Cache des dépendances : accélérer le build

Avec des centaines de paquets npm, réinstaller à chaque run CI coûte cher. Sur un projet, l’installation prenait 3 minutes pour 1 minute de tests — 75 % du temps perdu à l’install.

GitHub Actions peut stocker les dépendances déjà installées. Le plus simple : le cache intégré de setup-node.

- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'  # cache npm automatique

Une ligne cache: 'npm' : au premier run, installation normale + mise en cache de node_modules. Aux runs suivants, si package-lock.json est inchangé, restauration depuis le cache — de 3 minutes à environ 10 secondes.

Avec pnpm ou yarn : cache: 'pnpm' ou cache: 'yarn'.

Pour un contrôle fin, actions/cache :

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: ~/.npm         # cache global npm
    key: npm-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-

key identifie le cache de façon unique — ici le hash de package-lock.json : si le lock change, le cache est invalidé. restore-keys sert de repli pour retrouver un cache proche.

Avec un bon taux de hit, le gain est net. Mesure sur un projet : 4 minutes sans cache, 1,5 minute avec cache. À 20 builds par jour, le temps économisé vaut bien un article.

Chapitre 3 : Stratégie Matrix — tests en parallèle

C’est la fonctionnalité GitHub Actions que je préfère, et le cœur de cet article. Matrix transforme un job en plusieurs jobs parallèles pour tester versions, OS, etc. Un push peut lancer une dizaine de builds en quelques secondes ; le temps total reste proche du job le plus lent, pas de la somme — souvent plus de 60 % de gain par rapport au séquentiel.

Qu’est-ce que Matrix

Vous devez vérifier le projet sur Node 16, 18 et 20. L’approche classique : trois jobs dupliqués, ou un seul job qui change de version l’une après l’autre — config lourde ou temps long.

Matrix, c’est un tableau : en colonne les versions Node, en ligne les OS ; chaque case est un job de test indépendant. GitHub Actions génère toutes les combinaisons et les exécute en parallèle.

strategy:
  matrix:
    node: [16, 18, 20]
    os: [ubuntu-latest, windows-latest]

Cette config produit 6 jobs : Node 16 sur Ubuntu, Node 16 sur Windows, Node 18 sur Ubuntu, etc. La durée totale dépend du job le plus lent, pas de la somme.

Matrice de versions : tester plusieurs Node

J’ai déjà vu ce cas : développement en Node 20, un utilisateur signale un échec en Node 18 — une API se comporte différemment. Des tests multi-versions en amont auraient évité la mise en prod du bug.

Avec Matrix, c’est simple :

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false    # un échec n'arrête pas les autres versions
      matrix:
        node-version: [16, 18, 20, 22]  # quatre versions à tester

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}  # version depuis la matrix
          cache: 'npm'
      - run: npm ci
      - run: npm test

Points clés :

  • matrix.node-version liste les versions à tester
  • ${{ matrix.node-version }} dans les steps : chaque job reçoit sa valeur
  • fail-fast: false : un échec n’arrête pas les autres versions (défaut true). Pour la compatibilité, gardez false pour voir tous les résultats.

include et exclude : exclure des combinaisons inutiles ou en ajouter.

strategy:
  matrix:
    node-version: [16, 18, 20]
    os: [ubuntu-latest, windows-latest]
    exclude:
      - node-version: 16      # exclut Node 16 + Windows
        os: windows-latest
    include:
      - node-version: 20      # Node 20 aussi sur macOS
        os: macos-latest

exclude retire des combinaisons ; include en ajoute. Contrôle fin de la couverture.

Matrice OS : tests multi-plateformes

Pour un outil CLI ou tout code qui tourne sur plusieurs OS, la matrice OS est pertinente.

jobs:
  test:
    runs-on: ${{ matrix.os }}  # OS depuis la matrix
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20]

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

À garder en tête :

Différences de plateforme : chemins \ vs / sous Windows, comportements d’outils CLI différents. Les tests cross-OS détectent ces problèmes tôt.

Coût : la facturation varie selon l’OS. Linux : quota gratuit de base ; Windows ×2 ; macOS ×10. Des runs macOS épuisent vite le quota mensuel.

Stratégies d’économie :

  • macOS seulement si le produit tourne vraiment sur Mac
  • workflow séparé avec workflow_dispatch pour les tests macOS
  • dépôt public si possible : minutes illimitées

Astuces de performance

Matrix parallélise, mais pas sans limite. GitHub plafonne le parallélisme par défaut. Vous pouvez le régler :

strategy:
  max-parallel: 4  # au plus 4 jobs en parallèle
  matrix:
    node-version: [16, 18, 20, 22]

Avec beaucoup de combinaisons (10+ jobs), max-parallel évite de tout lancer d’un coup et de grignoter le quota gratuit.

Cache par job Matrix : chaque job a son cache ; setup-node avec cache le gère. Avec package-lock.json et node-version stables, le taux de hit reste élevé.

Éviter les étapes redondantes : le lint n’a pas besoin de tourner sur chaque version :

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run lint  # lint une seule fois sur Node 20

  test:
    needs: lint  # tests après lint OK
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [16, 18, 20]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'
      - run: npm ci
      - run: npm test

Le lint une fois sur Node 20, les tests sur trois versions — plus efficace.

Mesure : sans Matrix, 3 versions en série ≈ 12 minutes ; avec Matrix parallèle ≈ 4 minutes (version la plus lente). 8 minutes gagnées ; à 10 builds par jour, ça fait une différence visible.

Chapitre 4 : Retours d’expérience et dépannage

Les trois chapitres précédents couvrent la configuration. Celui-ci regroupe une checklist sécurité, performance et dépannage — à garder sous la main quand quelque chose bloque.

Checklist sécurité

PratiqueDescriptionExemple
Déclarer explicitement permissionsNe pas compter sur les droits par défautpermissions: { contents: read }
Secrets pour les données sensiblesPas de clés API ou SSH en dur${{ secrets.API_KEY }}
Limiter les branches déclenchéesCI seulement où c’est nécessairebranches: [main]
Référencer les Actions par SHACommit précis plutôt qu’une étiquette modifiableactions/checkout@b4ffde65f46336ab88eb53be808477a39b6bc2b1
Définir un timeoutÉviter les runs bloqués qui consomment le quotatimeout-minutes: 15

Souvent oublié : la version des Actions. @v4 est pratique pour les mises à jour, mais une étiquette peut être déplacée — en théorie vers du code malveillant. Le SHA (@b4ffde65f...) est plus sûr pour la prod, même si les upgrades demandent plus de travail.

Checklist performance

AstuceEffetConfiguration
Cache des dépendances−50 % ou plus sur l’installcache: 'npm'
npm ci plutôt que installPlus rapide et déterministerun: npm ci
timeout-minutesLimite les runs bloquéstimeout-minutes: 15
Matrix en parallèle−60 %+ sur le temps totalstrategy.matrix
concurrency pour annuler les doublonsUne seule build active par brancheconcurrency.group: ${{ github.ref }}

concurrency est pratique : cinq push d’affilée sur la même branche lancent cinq builds par défaut. Avec concurrency, les quatre premières sont annulées ; seule la dernière tourne.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true  # annule les builds en cours obsolètes

Table de dépannage rapide

Message d’erreurCauseSolution
Permission deniedDroits insuffisantsVérifier permissions, ajouter ce qui manque
Cache not foundClé de cache non trouvéeVérifier la key, package-lock.json inchangé
npm ERR! networkTimeout réseauAugmenter le timeout ou miroir registry
Out of memoryMémoire Node insuffisanteNODE_OPTIONS=--max_old_space_size=4096
EACCES permission deniedDroits fichierchmod +x script.sh en tête de script
Error: Cannot find moduleDépendances incomplètesVérifier que npm ci a réussi dans les logs

Scénarios fréquents :

Timeout réseau : parfois le runner accède lentement au registry npm. Miroir dans .npmrc :

- name: Configure npm registry
  run: echo "registry=https://registry.npmmirror.com" > .npmrc

Mémoire insuffisante : gros build Node → OOM. Variable d’environnement :

env:
  NODE_OPTIONS: --max_old_space_size=4096  # alloue 4 Go de mémoire à Node

Cache absent : normal au premier run. Vérifiez que package-lock.json existe (npm ci en a besoin) et que cache sur setup-node correspond au gestionnaire (npm/pnpm/yarn).

Conclusion

En résumé : un YAML suffit pour un pipeline CI ; permissions au minimum ; variables d’environnement par niveaux ; le cache peut diviser par deux le temps d’install ; Matrix simplifie les tests multi-versions en parallèle.

Copiez le modèle du chapitre 1, adaptez la version Node et vos commandes, et votre projet a un CI. Lancez d’abord, affinez ensuite. Essayez Matrix même avec deux versions Node — voir plusieurs coches vertes apparaître en parallèle, c’est satisfaisant.

Si vous bloquez sur GitHub Actions, laissez un commentaire. J’ajouterai les cas récurrents au tableau du chapitre 4 pour que d’autres évitent les mêmes pièges.

Mettre en place un pipeline CI GitHub Actions

Construire de zéro un pipeline CI complet pour automatiser build et tests

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Créer le répertoire des workflows

    À la racine du projet, créez le répertoire `.github/workflows/` pour y placer tous les fichiers de workflow.
  2. 2

    Step 2: Rédiger la configuration CI de base

    Créez le fichier `ci.yml` avec les déclencheurs (push/PR), les permissions (principe du moindre privilège) et les étapes du job (checkout, setup-node, install, test, build).
  3. 3

    Step 3: Activer le cache des dépendances

    Ajoutez le paramètre `cache: 'npm'` à l'étape `setup-node` pour mettre en cache automatiquement les dépendances npm et accélérer les builds suivants.
  4. 4

    Step 4: Configurer les tests Matrix multi-versions

    Ajoutez `strategy.matrix` avec la liste des versions Node à tester (par ex. [16, 18, 20]) pour exécuter les tests en parallèle.
  5. 5

    Step 5: Commit et vérification des résultats

    Commitez la configuration, poussez sur GitHub, puis ouvrez l'onglet Actions pour suivre l'état du build et les logs.

FAQ

Quel est le quota gratuit mensuel de GitHub Actions ?
Pour les dépôts privés : 2 000 minutes gratuites par mois sur un runner Linux. Les dépôts publics sont illimités. Un runner Windows consomme le double d'un Linux ; macOS consomme 10 fois plus.
Sur quelles branches le workflow CI doit-il se déclencher ?
Recommandation : uniquement sur main/master et pour les PR ciblant main. Les branches de développement peuvent ignorer le CI pour économiser des ressources. Utilisez le filtre `paths` pour exclure les changements de documentation.
Pourquoi privilégier npm ci plutôt que npm install ?
npm ci est plus rapide et plus fiable : il installe strictement selon package-lock.json sans modifier le fichier de verrouillage, ce qui convient au CI. npm install peut mettre à jour les versions de dépendances et produire des builds non déterministes.
Combien de temps la stratégie Matrix peut-elle faire gagner ?
Mesures réelles : tester 3 versions Node en série prend 12 minutes ; en Matrix parallèle, environ 4 minutes (selon la version la plus lente), soit une réduction de plus de 60 %.
Pourquoi le cache ne fonctionne-t-il parfois pas ?
Le cache repose sur le hash de `package-lock.json`. Si le fichier de verrouillage change, le cache est invalidé. L'absence de cache au premier run est normale. Vérifiez que le paramètre `cache` correspond au gestionnaire de paquets (npm/pnpm/yarn).
Comment utiliser des informations sensibles (clé API, clé SSH) dans le CI ?
Utilisez GitHub Secrets : ajoutez la clé dans les paramètres du dépôt, puis référencez-la dans le workflow avec `${{ secrets.KEY_NAME }}`. Les Secrets sont masqués automatiquement dans les logs.

13 min de lecture · Publié le: 6 avr. 2026 · Mis à jour le: 30 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog