Aller au contenu
CI/CD & Automatisation medium

Workflows GitHub Actions : guide complet

33 min de lecture

Un workflow est un fichier texte qui décrit une suite d’actions automatisées. Quand un événement se produit (push, pull request, heure programmée), GitHub lit ce fichier et exécute les instructions qu’il contient.

Concrètement, un workflow répond à 4 questions :

QuestionSection YAMLExemple
QUAND s’exécuter ?on:À chaque push, sur les PR, à minuit…
s’exécuter ?runs-on:Ubuntu, Windows, macOS
QUOI faire ?steps:Tester, builder, déployer…
DANS QUELLES CONDITIONS ?if:Seulement sur main, si les tests passent…

Regardons un workflow réaliste et décortiquons chaque partie :

# ══════════════════════════════════════════════════════════════════════════════
# MÉTADONNÉES
# ══════════════════════════════════════════════════════════════════════════════
name: CI # Nom affiché dans l'interface GitHub
run-name: Tests pour ${{ github.ref_name }} # Nom dynamique de l'exécution
# ══════════════════════════════════════════════════════════════════════════════
# QUAND ? (Événements déclencheurs)
# ══════════════════════════════════════════════════════════════════════════════
on:
push:
branches: [main, develop] # Sur push vers ces branches
pull_request:
branches: [main] # Sur PR vers main
workflow_dispatch: # Bouton manuel dans l'interface
# ══════════════════════════════════════════════════════════════════════════════
# SÉCURITÉ (Permissions)
# ══════════════════════════════════════════════════════════════════════════════
permissions:
contents: read # Lecture du code (minimum nécessaire)
# ══════════════════════════════════════════════════════════════════════════════
# CONFIGURATION GLOBALE
# ══════════════════════════════════════════════════════════════════════════════
env:
NODE_VERSION: '20' # Utilisable dans tous les jobs
# ══════════════════════════════════════════════════════════════════════════════
# LES JOBS (Les unités de travail)
# ══════════════════════════════════════════════════════════════════════════════
jobs:
# Job 1 : Tests
test:
runs-on: ubuntu-24.04
steps:
# Actions épinglées par SHA (voir encart ci-dessous)
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm ci
- run: npm test
# Job 2 : Build (attend que test soit OK)
build:
needs: test # Dépendance explicite
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: npm ci
- run: npm run build

C’est la structure la plus importante à comprendre. Si vous ne retenez qu’une chose de ce guide, c’est celle-ci.

Pensez à un workflow comme une entreprise :

Workflow (L'entreprise)
├── Job 1 "test" (Un département)
│ ├── Step 1: Récupérer le code
│ ├── Step 2: Installer Node
│ ├── Step 3: Installer les dépendances
│ └── Step 4: Lancer les tests
├── Job 2 "build" (Un autre département)
│ ├── Step 1: Récupérer le code
│ ├── Step 2: Installer les dépendances
│ └── Step 3: Construire l'application
└── Job 3 "deploy" (Encore un autre)
└── Step 1: Déployer sur le serveur
ConceptEnvironnementExécutionPartage de fichiers
JobsMachines différentesEn parallèle (par défaut)Via artifacts uniquement
StepsMême machineSéquentiellementOui (même système de fichiers)

Conséquences pratiques :

  • Un fichier créé dans le step 1 est disponible dans le step 2 du même job
  • Un fichier créé dans le job A n’existe pas dans le job B (machines différentes !)
  • Pour qu’un job attende un autre, il faut le déclarer avec needs:
  • Pour partager des fichiers entre jobs, il faut utiliser les artifacts

L’isolation entre jobs est une feature de sécurité et de fiabilité :

  • Parallélisation : les jobs peuvent tourner sur des machines différentes, simultanément
  • Reproductibilité : chaque job démarre avec un environnement propre
  • Isolation des échecs : un job planté ne corrompt pas les autres

Si vous ne comprenez pas cette distinction, vous ferez cette erreur :

# ❌ ERREUR : les fichiers buildés dans "build" ne sont PAS disponibles dans "deploy"
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: npm run build # Crée un dossier dist/
deploy:
needs: build # Attend que build soit terminé...
runs-on: ubuntu-24.04 # ...mais c'est une AUTRE MACHINE !
steps:
- run: ./deploy.sh dist/ # ❌ Erreur : dist/ introuvable

Pourquoi ça ne marche pas ? Le job deploy tourne sur une nouvelle machine vierge. Le dossier dist/ créé par build est resté sur l’ancienne machine (qui a été détruite).

La solution : utiliser les artifacts pour “télécharger” les fichiers d’un job à l’autre. Voir Artifacts vs Cache.

La section on: définit quand votre workflow démarre. C’est le “déclencheur” qui réveille votre automatisation.

DéclencheurQuand ça se déclencheCas d’usage typique
pushCode poussé sur une brancheTests, build
pull_requestPR ouverte ou mise à jourValidation avant merge
workflow_dispatchBouton manuelDéploiement à la demande
scheduleHeure programmée (cron)Scans de sécurité nocturnes
releaseRelease publiéePublication de packages
on:
# Quand quelqu'un pousse du code
push:
branches:
- main # Branche exacte
- 'release/*' # Wildcard : release/v1, release/v2...
paths:
- 'src/**' # Seulement si des fichiers dans src/ changent
- '!**/*.md' # Mais pas les fichiers Markdown
# Quand une PR est ouverte ou mise à jour
pull_request:
branches: [main]
types: [opened, synchronize, reopened]

Déclenchement manuel (très pratique pour les déploiements)

Section intitulée « Déclenchement manuel (très pratique pour les déploiements) »
on:
workflow_dispatch:
inputs:
environment:
description: 'Où déployer ?'
required: true
type: choice
options:
- staging
- production

Vous verrez alors un bouton “Run workflow” dans l’interface GitHub avec un menu déroulant pour choisir l’environnement.

on:
schedule:
# Tous les jours à 3h du matin (UTC)
- cron: '0 3 * * *'

Un job est un ensemble de steps qui s’exécutent sur une même machine virtuelle. C’est l’unité de base de votre workflow.

  • Chaque job démarre sur une machine vierge (fresh VM)
  • Les jobs s’exécutent en parallèle par défaut
  • Un job peut dépendre d’autres jobs (avec needs:)
  • Un job peut avoir ses propres permissions (plus restrictives que le workflow)
  • Un job peut cibler un environment spécifique (staging, production)
jobs:
mon-job: # Identifiant unique (lettres, chiffres, tirets, _)
name: "Mon super job" # Nom affiché dans l'UI (optionnel mais recommandé)
runs-on: ubuntu-24.04 # Machine d'exécution (obligatoire)
# Options de comportement
timeout-minutes: 30 # Limite de temps (défaut: 6 heures !)
continue-on-error: false # Arrêter le workflow si ce job échoue (défaut)
# Condition d'exécution
if: github.event_name == 'push'
# Variables d'environnement pour ce job uniquement
env:
DEBUG: true
# Permissions spécifiques (plus restrictives que le workflow)
permissions:
contents: read
# Environnement cible (optionnel, pour les déploiements)
environment: staging
# Les étapes à exécuter
steps:
- run: echo "Je suis une étape"
PropriétéObligatoireDescription
runs-onOuiMachine virtuelle (ubuntu-24.04, windows-2022, macos-14)
stepsOuiListe des étapes à exécuter
nameNonNom affiché dans l’interface (recommandé)
needsNonJobs à attendre avant de démarrer
ifNonCondition d’exécution
timeout-minutesNonDurée max avant échec (défaut: 360 min !)
permissionsNonPermissions GITHUB_TOKEN pour ce job
environmentNonEnvironnement cible (staging, production…)
envNonVariables d’environnement

Par défaut, tous les jobs démarrent en même temps. Pour créer une séquence, utilisez needs: :

jobs:
test:
runs-on: ubuntu-24.04
steps:
- run: npm test
build:
needs: test # Attend que "test" soit terminé et réussi
runs-on: ubuntu-24.04
steps:
- run: npm run build
deploy:
needs: [test, build] # Attend PLUSIEURS jobs
runs-on: ubuntu-24.04
steps:
- run: ./deploy.sh

Visualisation de l’exécution :

test ──────────┐
├──→ deploy
build ─────────┘
│ (build attend test)

Les steps (étapes) sont les actions concrètes que vous voulez exécuter. Chaque step fait une chose : cloner le code, installer des dépendances, lancer des tests, déployer…

Il existe deux types de steps, et vous les utiliserez constamment :

TypeSyntaxeUsageExemple
Actionuses:Composant réutilisableCloner le repo, setup Node…
Commanderun:Script shellnpm install, pytest, make
steps:
# ══════════════════════════════════════════════════════════════════════════════
# TYPE 1 : Action (uses) — composant réutilisable
# ══════════════════════════════════════════════════════════════════════════════
- name: Récupérer le code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: # Paramètres de l'action
fetch-depth: 0
- name: Configurer Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'npm' # Active le cache des dépendances
# ══════════════════════════════════════════════════════════════════════════════
# TYPE 2 : Commande shell (run) — script à exécuter
# ══════════════════════════════════════════════════════════════════════════════
- name: Installer les dépendances
run: npm ci
# Plusieurs commandes avec le pipe |
- name: Build et test
run: |
echo "Building..."
npm run build
echo "Testing..."
npm test

Chaque step peut avoir des propriétés qui contrôlent son comportement :

PropriétéDescriptionExemple
nameNom affiché dans les logs"Run unit tests"
ifCondition d’exécutionif: success(), if: failure()
continue-on-errorNe pas échouer le job si ce step échouetrue
timeout-minutesDurée max du step15
working-directoryDossier d’exécution./frontend
envVariables d’environnement pour ce stepDEBUG: true
steps:
# Exécuter seulement si les steps précédents ont réussi (défaut)
- name: Déployer
if: success()
run: ./deploy.sh
# Exécuter TOUJOURS (même si un step a échoué)
- name: Nettoyer
if: always()
run: rm -rf temp/
# Exécuter seulement en cas d'échec
- name: Notifier l'équipe
if: failure()
run: ./notify-slack.sh "Le build a échoué !"
# Continuer même si ce step échoue
- name: Analyse optionnelle
continue-on-error: true
run: npm run analyze
# Timeout personnalisé (utile pour les tests lents)
- name: Tests d'intégration
timeout-minutes: 15
run: npm run test:integration
# Exécuter dans un dossier spécifique
- name: Tests frontend
working-directory: ./frontend
run: npm test

GitHub met à disposition des variables de contexte qui contiennent des informations sur l’exécution en cours. Vous les utilisez avec la syntaxe ${{ contexte.variable }}.

ContexteCe qu’il contientExemples
githubInfos sur l’événementgithub.ref_name, github.actor, github.sha
secretsVos secretssecrets.DOCKER_TOKEN
varsVos variablesvars.NODE_VERSION
envVariables d’environnementenv.MY_VAR
matrixValeur courante de la matrixmatrix.os, matrix.node
needsOutputs des jobs précédentsneeds.build.outputs.version
# Afficher des infos sur le contexte
- name: Debug
run: |
echo "Branche: ${{ github.ref_name }}"
echo "Auteur: ${{ github.actor }}"
echo "SHA: ${{ github.sha }}"
# Utiliser un secret
- name: Login Docker
run: echo ${{ secrets.DOCKER_TOKEN }} | docker login -u user --password-stdin
# Condition basée sur le contexte
- name: Deploy (main only)
if: github.ref_name == 'main'
run: ./deploy.sh
# Utiliser une variable d'environnement
env:
NODE_VERSION: '20'
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ env.NODE_VERSION }}

Ces règles vous éviteront 90% des problèmes :

Évitez les workflows géants qui font tout. Préférez plusieurs fichiers spécialisés :

.github/workflows/
├── ci.yml # Tests et build
├── security.yml # Scans de vulnérabilités
├── deploy.yml # Déploiement
└── release.yml # Publication de versions

Pourquoi ? Un workflow qui échoue vous dit immédiatement est le problème. “Le déploiement a échoué” est plus utile que “le workflow CI a échoué”.

C’est l’erreur n°1 des débutants :

# ❌ Le code n'est PAS là par magie !
steps:
- run: npm test # Erreur : package.json introuvable
# ✅ Toujours checkout d'abord
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: npm test

Déclarez toujours les permissions explicitement et au minimum nécessaire :

permissions:
contents: read # Lecture seule par défaut
jobs:
publish:
permissions:
contents: read
packages: write # Écriture seulement pour le job qui publie
steps: [...]

Voir Sécurité GitHub Actions pour les détails.

Les logs sont votre outil de debug. Des noms explicites vous font gagner du temps :

# ❌ Difficile à lire dans les logs (et pas épinglé !)
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
# ✅ Logs clairs, compréhensibles et sécurisés
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm test

Pour résumer, voici un exemple de workflow complet et sécurisé pour une application Node.js :

name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test

Ces erreurs, tout le monde les fait au début. Comprendre pourquoi elles se produisent vous aidera à les éviter — et à les corriger rapidement quand elles arrivent.

Le symptôme : votre workflow échoue immédiatement avec “file not found”, “package.json not found” ou “command not found”.

Pourquoi ça arrive : un runner GitHub Actions démarre avec une machine vide. Votre code n’est pas là par magie — il faut explicitement le télécharger depuis le repository avec actions/checkout.

# ❌ Le runner est vide, le code n'existe pas !
steps:
- run: npm test # Erreur : package.json introuvable
# ✅ On récupère d'abord le code, puis on travaille
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- run: npm test # Maintenant package.json existe

Comment s’en souvenir : pensez à un coursier qui doit récupérer un colis avant de le livrer. Le checkout, c’est aller chercher le colis (votre code).

Le symptôme : votre clé API, token ou mot de passe apparaît en clair dans les logs du workflow — visible par tous ceux qui ont accès au repository.

Pourquoi ça arrive : GitHub masque automatiquement les secrets (***) seulement quand ils sont utilisés via le contexte secrets. Si vous les copiez dans une variable ou les affichez avec echo, le masquage ne fonctionne plus.

# ❌ DANGER ! Le secret apparaît en clair dans les logs
- run: echo "Token: ${{ secrets.API_KEY }}"
# ❌ DANGER ! Copier dans une variable puis echo = pas de masquage
- run: |
TOKEN=${{ secrets.API_KEY }}
echo "Utilisation de $TOKEN" # Le token est visible !
# ✅ Passer via env : le masquage fonctionne
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
run: ./deploy.sh # Le script lit $API_KEY, jamais affiché

La règle d’or : ne jamais echo un secret, même pour debug. Si vous devez vérifier qu’un secret existe, testez sa longueur :

- run: |
if [ -n "$API_KEY" ]; then
echo "Secret présent (${#API_KEY} caractères)"
fi
env:
API_KEY: ${{ secrets.API_KEY }}

Le symptôme : votre job de déploiement démarre alors que le build n’est pas terminé, ou échoue car les fichiers produits par le job précédent n’existent pas.

Pourquoi ça arrive : par défaut, tous les jobs d’un workflow démarrent en même temps (parallèlement). C’est voulu pour aller plus vite, mais il faut explicitement déclarer les dépendances avec needs:.

# ❌ build et deploy démarrent EN MÊME TEMPS !
jobs:
build:
runs-on: ubuntu-24.04
steps:
- run: npm run build # Crée dist/
deploy:
runs-on: ubuntu-24.04
steps:
- run: ./deploy.sh dist/ # dist/ n'existe pas encore !
# ✅ deploy attend que build soit terminé
jobs:
build:
runs-on: ubuntu-24.04
steps:
- run: npm run build
deploy:
needs: build # ← Attend la fin de build
runs-on: ubuntu-24.04
steps:
- run: ./deploy.sh dist/

Attention : même avec needs:, les fichiers créés dans build ne sont pas disponibles dans deploy (machines différentes). Utilisez les artifacts pour partager des fichiers entre jobs.

Le symptôme : un job bugué (boucle infinie, attente d’une ressource qui ne répond pas) tourne pendant des heures, consommant vos minutes GitHub Actions.

Pourquoi ça arrive : le timeout par défaut est de 6 heures (360 minutes). Un simple bug peut coûter cher en temps et en argent.

# ❌ Si npm test boucle, le job tourne 6 heures !
jobs:
test:
runs-on: ubuntu-24.04
steps:
- run: npm test
# ✅ Timeout explicite : échec rapide si problème
jobs:
test:
runs-on: ubuntu-24.04
timeout-minutes: 15 # Échoue après 15 min max
steps:
- run: npm test

Recommandations de timeout :

Type de jobTimeout suggéré
Tests unitaires10-15 min
Build application15-20 min
Tests d’intégration20-30 min
Déploiement10-15 min
ErreurSymptômeSolution
Pas de checkout”file not found”Ajouter actions/checkout en premier
Secret exposéToken visible dans les logsUtiliser env: au lieu de echo
Jobs parallèlesDéploiement avant fin du buildAjouter needs: job_precedent
Pas de timeoutJob qui tourne des heuresAjouter timeout-minutes:

Vous avez écrit un workflow, vous le poussez sur GitHub, et… erreur de syntaxe. Vous corrigez, re-poussez, nouvelle erreur. Ce cycle frustrant est évitable.

Deux outils complémentaires permettent de valider vos workflows localement :

OutilCe qu’il faitQuand l’utiliser
actionlintAnalyse statique (syntaxe, types, références)Avant chaque commit
ScorecardAudit sécurité (permissions, épinglage, patterns dangereux)Avant mise en production

actionlint détecte les erreurs sans exécuter le workflow : syntaxe YAML, propriétés mal orthographiées, actions inexistantes, expressions invalides, problèmes de sécurité.

Fenêtre de terminal
# Installation
brew install actionlint # macOS
# ou
go install github.com/rhysd/actionlint/cmd/actionlint@latest
# Validation
actionlint # Tous les workflows
actionlint .github/workflows/ci.yml # Un fichier spécifique

Exemple de sortie :

.github/workflows/ci.yml:15:9: property "node-verion" is not defined [expression]
.github/workflows/ci.yml:23:7: "actions/checkout@v4" is not pinned by SHA [security]

L’erreur est trouvée en quelques millisecondes, pas après un push et une attente de plusieurs minutes.

OpenSSF Scorecard audite la posture sécurité de vos workflows. Trois checks sont particulièrement utiles :

CheckCe qu’il vérifie
Token-PermissionsPermissions write au niveau job (pas workflow)
Pinned-DependenciesActions épinglées par SHA
Dangerous-WorkflowPatterns dangereux (pull_request_target + checkout fork)
Fenêtre de terminal
# Installation
brew install scorecard # macOS
# ou
go install github.com/ossf/scorecard/v5/cmd/scorecard@latest
# Audit des workflows
scorecard --local . --checks Token-Permissions,Pinned-Dependencies,Dangerous-Workflow
# Audit complet avec détails
scorecard --local . --show-details

Exemple de sortie :

Token-Permissions: 10/10
✅ permissions are declared at job level
Pinned-Dependencies: 6/10
⚠️ actions/checkout@v4 is not pinned by SHA
✅ actions/setup-node@39370e... is pinned
Dangerous-Workflow: 10/10
✅ no dangerous patterns detected

Objectif : visez 8+ sur chaque check. Un score de 10/10 sur Token-Permissions et Dangerous-Workflow est atteignable facilement.

Avant chaque push, exécutez ce script :

validate-workflows.sh
#!/bin/bash
echo "🔍 Validation statique avec actionlint..."
actionlint
if [ $? -ne 0 ]; then
echo "❌ Erreurs de syntaxe détectées"
exit 1
fi
echo "� Audit sécurité avec Scorecard..."
scorecard --local . --checks Token-Permissions,Pinned-Dependencies,Dangerous-Workflow
if [ $? -ne 0 ]; then
echo "⚠️ Problèmes de sécurité détectés (voir ci-dessus)"
fi
echo "�🚀 Test d'exécution avec act..."
act -n
if [ $? -ne 0 ]; then
echo "❌ Erreurs d'exécution détectées"
exit 1
fi
echo "✅ Workflows valides !"

Vous avez maintenant toutes les bases pour créer vos propres workflows. La théorie, c’est bien — mais rien ne vaut la pratique pour ancrer les concepts.

Le TP 01 : Votre premier workflow vous met dans une situation réaliste : vous rejoignez une équipe où les tests sont lancés manuellement (spoiler : ça ne marche jamais). Votre mission : automatiser tout ça.

Ce que vous allez faire :

  • Créer un workflow de A à Z
  • Configurer le déclenchement sur push
  • Rechercher sur la GitHub Marketplace les actions dont vous avez besoin (checkout, setup-python)
  • Ajouter les steps pour :
    • Cloner le code
    • Installer Python
    • Installer les dépendances
    • Lancer les tests
  • Valider votre travail localement avant de pousser (avec actionlint et act)

Durée estimée : 30 minutes

TP 01 : Votre premier workflow

Automatisez l’exécution des tests d’une application Node.js. Vous appliquerez tout ce que vous venez d’apprendre : structure du workflow, checkout, permissions, bonnes pratiques.

Accéder au TP 01

Testez vos connaissances sur les workflows GitHub Actions. Ce quiz couvre les tout ce qui a été vu dans fondations (la sécurité, les secrets) et le contenu de ce guide.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

25 questions
15 min.
70%

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre