Leçon 3 / 6
Leçon 03 · GitHub Actions

Jobs, steps et runners

Job vs step : quelle différence ?

Dans GitHub Actions, un workflow contient des jobs, et chaque job contient des steps. Ce n'est pas la même chose :

  • Un job s'exécute sur une machine virtuelle dédiée (un runner). Chaque job démarre sur un environnement propre et neuf.
  • Un step est une étape individuelle à l'intérieur d'un job. Tous les steps d'un même job partagent le même système de fichiers et le même environnement.

Conséquence directe : si tu veux qu'une étape utilise un fichier créé par une étape précédente, ils doivent être dans le même job. Entre jobs différents, les fichiers ne sont pas partagés automatiquement.

💡

Un step peut être une commande shell (run:) ou une action réutilisable du marketplace (uses:). Les deux coexistent librement dans un même job.

Les runners : où s'exécute ton code ?

Un runner est la machine virtuelle mise à disposition par GitHub pour exécuter tes jobs. Tu choisis le système d'exploitation avec la clé runs-on.

Runners hébergés par GitHub (GitHub-hosted)

  • ubuntu-latest — Linux Ubuntu, le plus rapide et le plus utilisé. Idéal pour CI standard.
  • windows-latest — Windows Server. Utile pour les apps .NET, Electron ou les scripts PowerShell.
  • macos-latest — macOS. Nécessaire pour compiler des apps iOS/macOS. Plus lent et plus cher en minutes.
⚠️

Les minutes GitHub Actions sont gratuites pour les dépôts publics. Pour les dépôts privés, les runners macOS consomment 10× plus de minutes que Linux, et Windows 2× plus. Utilise ubuntu-latest par défaut sauf besoin spécifique.

Self-hosted runners

Tu peux aussi connecter ta propre machine comme runner. Utilise runs-on: self-hosted ou un tableau de labels comme runs-on: [self-hosted, linux, x64]. C'est utile pour des raisons de performance, de coût ou d'accès à des ressources internes.

Jobs en parallèle (comportement par défaut)

Par défaut, tous les jobs d'un workflow s'exécutent en parallèle. C'est idéal pour gagner du temps : lancer les tests frontend et backend en même temps, par exemple.

YAML — jobs parallèles
name: CI parallèle

on: push

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Lint du code
        run: npm run lint

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Tests unitaires
        run: npm test

  # lint et test démarrent en même temps — pas de dépendance entre eux

Jobs séquentiels avec needs:

Pour qu'un job attende qu'un autre soit terminé, utilise la clé needs:. C'est indispensable pour un pipeline de déploiement : on ne déploie que si les tests passent.

YAML — pipeline lint → test → build
name: Pipeline CI

on: push

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Vérification du code
        run: npm run lint

  test:
    runs-on: ubuntu-latest
    needs: lint            # démarre seulement si lint réussit
    steps:
      - uses: actions/checkout@v4
      - name: Tests
        run: npm test

  build:
    runs-on: ubuntu-latest
    needs: test            # démarre seulement si test réussit
    steps:
      - uses: actions/checkout@v4
      - name: Build production
        run: npm run build

  # Ordre d'exécution : lint → test → build (chaîne séquentielle)

needs: accepte aussi un tableau : needs: [lint, test] si un job doit attendre plusieurs jobs en parallèle avant de démarrer.

Réutiliser les actions du marketplace

Au lieu de réinventer la roue, GitHub met à disposition des milliers d'actions prêtes à l'emploi sur le marketplace. Les deux indispensables pour un projet Node.js :

  • actions/checkout@v4 — clone ton dépôt sur le runner. Presque toujours en premier step.
  • actions/setup-node@v4 — installe Node.js dans la version souhaitée et configure le cache npm.
YAML — checkout + setup-node
jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      # 1. Cloner le dépôt
      - name: Checkout du code
        uses: actions/checkout@v4

      # 2. Installer Node.js 20 avec cache npm
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'    # met en cache node_modules entre les runs

      # 3. Installer les dépendances
      - name: Installation des dépendances
        run: npm ci

      # 4. Lancer les tests
      - name: Tests
        run: npm test
💡

Toujours épingler les actions avec une version précise (@v4) plutôt que @main ou @latest. Cela évite les régressions si l'action est mise à jour avec un changement incompatible.

Passer des données entre steps : outputs et env

Les steps d'un même job peuvent se partager des données de deux façons :

Variables d'environnement avec env:

Tu peux définir des variables d'environnement au niveau du job entier (disponibles dans tous ses steps) ou d'un step spécifique.

YAML — variables d'environnement
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      NODE_ENV: production   # disponible dans tous les steps

    steps:
      - uses: actions/checkout@v4

      - name: Générer la version
        run: |
          VERSION=$(node -p "require('./package.json').version")
          # Écrire dans $GITHUB_ENV pour partager avec les steps suivants
          echo "APP_VERSION=$VERSION" >> $GITHUB_ENV

      - name: Utiliser la version
        run: echo "Déploiement version $APP_VERSION"

Outputs entre steps et entre jobs

Pour partager une valeur calculée entre jobs (pas seulement entre steps), utilise le mécanisme outputs :

YAML — outputs entre jobs
jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.get-version.outputs.version }}
    steps:
      - id: get-version
        run: |
          VERSION=$(node -p "require('./package.json').version")
          echo "version=$VERSION" >> $GITHUB_OUTPUT

  deploy:
    runs-on: ubuntu-latest
    needs: prepare
    steps:
      - name: Déployer
        run: echo "Version ${{ needs.prepare.outputs.version }}"

Les conditions avec if:

Tu peux conditionner l'exécution d'un step ou d'un job entier avec la clé if:. Quelques expressions courantes :

YAML — conditions if:
jobs:
  deploy:
    runs-on: ubuntu-latest
    # Ce job tourne seulement sur la branche main
    if: github.ref == 'refs/heads/main'
    steps:
      - uses: actions/checkout@v4

      - name: Build
        run: npm run build

      # Step qui tourne seulement si les étapes précédentes ont réussi
      - name: Déployer en prod
        if: success()
        run: ./deploy.sh

      # Step qui tourne UNIQUEMENT en cas d'échec — pour notifier
      - name: Notifier en cas d'erreur
        if: failure()
        run: echo "Le déploiement a échoué !"

Les fonctions de statut les plus utiles dans if: :

  • success() — vrai si tous les steps précédents ont réussi (comportement par défaut)
  • failure() — vrai si au moins un step précédent a échoué
  • always() — tourne toujours, même si le job est annulé
  • cancelled() — vrai si le workflow a été annulé manuellement

Le contexte github donne accès à plein d'informations sur le déclencheur : github.ref (branche/tag), github.actor (qui a déclenché), github.event_name (push, pull_request…), github.sha (commit SHA).

// à retenir
  • Job = machine virtuelle dédiée · Step = étape dans un job (même système de fichiers).
  • runs-on: ubuntu-latest pour la grande majorité des cas — rapide et gratuit.
  • Sans needs:, les jobs tournent en parallèle. Avec needs: autrejob, ils sont séquentiels.
  • actions/checkout@v4 et actions/setup-node@v4 sont les actions de base pour tout projet JS/TS.
  • Partage de données : $GITHUB_ENV pour les variables, $GITHUB_OUTPUT pour les outputs inter-jobs.
  • if: failure() pour les steps de notification d'erreur · if: github.ref == 'refs/heads/main' pour restreindre au déploiement.