Construire un pipeline CI/CD automatisé avec GitHub Actions
Ce guide met en place un pipeline GitHub Actions qui build, teste sur plusieurs versions, et déploie à chaque push, avec des secrets et des permissions gérés correctement dès le départ.
Les déclencheurs : décider quand le pipeline s'exécute
Chaque workflow commence par on:. Pour une application web classique, deux déclencheurs suffisent : push sur votre branche principale (pour que les merges se déploient automatiquement) et pull_request (pour obtenir un retour build/tests avant le merge, pas après).
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: readCe bloc permissions au niveau racine est volontaire, ce n'est pas du remplissage.
Les permissions par défaut du GITHUB_TOKEN de GitHub étaient historiquement larges (write sur presque tout), sauf si votre organisation a modifié ce comportement. Définissez toujours explicitement permissions dans le workflow, même si c'est juste contents: read.
Le job de build et de tests
C'est le job que chaque pull request exécute. Gardez-le déterministe : figez la version du runtime, mettez en cache les dépendances, et échouez bruyamment.
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --ci
- name: Build
run: npm run buildnpm ci plutôt que npm install : cette commande installe exactement ce qui est décrit dans le lockfile et échoue si celui-ci n'est pas synchronisé, au lieu de le réécrire silencieusement.
Les secrets : les utiliser sans les exposer
Tôt ou tard, ce job aura besoin d'une véritable credential (une clé API pour un service tiers pendant les tests, ou des identifiants de déploiement plus loin dans le pipeline). GitHub Actions met à disposition secrets.* pour cela, et il n'y a en réalité que deux règles qui comptent vraiment.
Premièrement, ne jamais afficher un secret, même pour du debug. run: echo ${{ secrets.API_KEY }} l'imprime directement dans le log du build. Ce log est visible par toute personne ayant un accès en lecture au dépôt, et il y reste pendant toute la durée de rétention. Deuxièmement, transmettez les secrets sous forme de variables d'environnement à l'étape qui en a besoin, plutôt que comme arguments en ligne de commande. Des arguments de ligne de commande peuvent se retrouver dans des listes de processus ou un historique shell capturé ailleurs dans le log.
- name: Run integration tests
env:
API_KEY: ${{ secrets.API_KEY }}
run: npm run test:integrationGitHub masque automatiquement les valeurs des secrets enregistrés dans les logs. Mais ce masquage n'est qu'une correspondance de chaîne de caractères : transformez d'abord le secret (encodage base64, découpage, journalisation dans un objet JSON), et le masquage peut passer à côté.
N'utilisez jamais pull_request_target pour exécuter du code non fiable provenant d'un fork avec accès à vos secrets. pull_request_target s'exécute avec les permissions et les secrets du dépôt de base. Combinez cela à un checkout puis une exécution du code du fork, et vous obtenez une méthode bien connue pour exfiltrer des secrets depuis des dépôts publics. Si vous devez tester des PR issues de forks, utilisez pull_request (sans secrets, avec un token restreint) et un workflow séparé, validé manuellement, pour tout ce qui nécessite de vraies credentials.
Tester sur plusieurs versions avec une matrix
Si vous maintenez une librairie, ou que vous n'avez pas encore arrêté la version de runtime à standardiser, un build matriciel exécute le même job sur plusieurs configurations en parallèle, au lieu de vous obliger à maintenir des jobs copiés-collés.
jobs:
build-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ["18", "20", "22"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm ci
- run: npm test -- --ci
- run: npm run buildCela produit trois jobs en parallèle (build-and-test (18), (20), (22)). C'est le moyen le plus rapide de détecter un "ça marche sur Node 20, ça casse sur Node 18" avant qu'un utilisateur ne vous le signale.
Faire circuler des artefacts entre les jobs
Le résultat du build produit par le job de tests est souvent exactement ce que vous voulez déployer ; inutile de le reconstruire dans le job de déploiement, au risque d'obtenir un résultat légèrement différent. Publiez-le comme artefact, puis récupérez-le dans le job qui en a besoin.
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: production-build
path: dist/
retention-days: 1Et dans le job de déploiement :
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: production-build
path: dist/Le job de déploiement : contrôlé, pas automatique par défaut
Le déploiement devrait dépendre de la réussite du job de build/tests, et idéalement d'une validation explicite pour la production. needs: gère la première partie ; un environment GitHub avec des reviewers obligatoires gère la seconde.
jobs:
deploy:
needs: build-and-test
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment: production
permissions:
contents: read
id-token: write
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: production-build
path: dist/
- name: Deploy
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: ./scripts/deploy.shLa condition if fait que le job de déploiement ne s'exécute que sur les push vers main. La ligne environment: production est ce qui vous permet de configurer des reviewers obligatoires et des restrictions de branches de déploiement dans les paramètres du dépôt. Ajoutez une protection de branche sur main (exiger que le check build-and-test passe, exiger une review avant merge), et la boucle se ferme : aucun code non testé n'atteint main.
Mis bout à bout, le pipeline ressemble à ceci :
Une fois les déclencheurs, le job de tests qui échoue vraiment, et la gestion des secrets bien posés, chaque ajout suivant, qu'il s'agisse du staging, d'un canary ou d'une notification Slack, n'est jamais qu'un job de plus accroché au même graphe.
Envie de faire tourner ça en production ?
Ce tutoriel couvre les concepts et l'architecture. Si vous voulez l'implémenter dans votre propre infrastructure, ou monter en compétence pour posséder ce sujet durablement, je propose du mentorat individuel construit autour de votre environnement réel, pas une formation générique.
Ce tutoriel
- Concepts clés et architecture principale
- Extraits de code illustratifs
- Le raisonnement derrière chaque décision
Mentorat individuel
- Des sessions de travail sur votre propre environnement
- Des réponses directes aux cas particuliers que vous rencontrez
- Un retour sur votre implémentation réelle
- Un accompagnement continu pendant que vous la construisez