Si vous utilisez GitHub et que vous vous intéressez un peu au monde du DevOps, vous avez forcément croisé GitHub Actions. Et parfois… on se dit qu’on aurait préféré que nos chemins ne se soient jamais rencontrés.

Sur internet, les avis sont très partagés : certains adorent, d’autres jurent que c’est l’enfer. La vérité, c’est que beaucoup des critiques viennent surtout d’une mauvaise utilisation de l’outil.

Dans cet article, je vous raconte mon parcours avec GitHub Actions, présenté sous la forme d’une “semaine fictive” inspirée de la Genèse : mes débuts chaotiques, mes erreurs, mes découvertes… et comment j’ai fini par passer de l’enfer au paradis.

Jour 1 : La rencontre

Ma première rencontre avec le monde du CI/CD s’est faite avec GitHub Actions. Et, de manière totalement illogique, ma première idée a été de lancer… des tests automatisés. Sauf qu’à l’époque, je n’écrivais quasiment pas de tests. Allez comprendre.

Après une lecture en diagonale de la documentation et quelques essais maladroits, j’ai réussi à obtenir un workflow fonctionnel :

name: CI

on:
  push:
    branches: [ "master", "dev" ]
  pull_request:
    branches: [ "master", "dev" ]

# ...

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x, 18.x]

    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm ci
    - run: npx prisma migrate dev --name init
    - run: npm run build --if-present
    - run: npm test

Tous les voyants étaient au vert. J’étais fier de moi, au sommet de la fameuse “montagne de la stupidité”. J’étais donc encore bien loin d’imaginer la suite.

Avec le recul, je vois bien les problèmes :

  • Un seul job fourre-tout : tout était dans “build”, alors que ça faisait plein d’autres choses. Pas très clair.
  • Des étapes sans vraie logique : pourquoi lancer une migration Prisma dans un job de CI ?
  • Nom trompeur : “build” laissait penser qu’on faisait uniquement de la compilation, alors que c’était un joyeux mélange de build, tests et scripts divers.

Bref, un workflow petit mais déjà bancal. Et comme souvent, quand on commence mal, ça finit rarement bien…

Jour 2 : Les choses sérieuses

Dans GitHub Actions, il y a le mot “action”. Et ça peut vouloir dire deux choses :

  1. L’action au sens événement → réaction : un push, un pull request, et hop, ça déclenche un workflow.
  2. L’action au sens GitHub : comme le dit la doc, “une extension réutilisable qui peut simplifier votre workflow”.

L’idée est simple : des éditeurs (ou la communauté) créent et testent ces actions, et nous, développeurs, on peut les intégrer directement dans nos workflows sans réinventer la roue. Tout est disponible sur la Marketplace GitHub.

La philosophie est séduisante : un assemblage de “boîtes noires”, chacune spécialisée dans une tâche bien précise.

Mon cas concret : Docker

Pour l’un de mes projets, je voulais automatiser la construction et le déploiement d’images Docker. Objectif : faire ça dans les règles de l’art. Après une petite exploration de la marketplace… bingo ! Docker fournit ses propres actions officielles. Résultat, j’ai rapidement obtenu ce workflow :

name: Build docker image & deploy

# ...

jobs:
  docker:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: # ...
          password: # ...
      - name: Build and push
        uses: docker/build-push-action@v6
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: # ...

Sur le papier, c’est nickel. Mais dans les faits… comment je teste que ce workflow fonctionne ?

Le vrai problème : tester

À ce stade, il n’y a qu’une seule solution : commit et push sur le dépôt. Et si ça ne marche pas ? On lit les logs, on change et on recommence.

Imaginez : on vous demande de développer une feature et la seule façon de la tester, c’est de la déployer en production à chaque essai. Vous voyez le souci ?

Alors oui, ce n’est pas dramatique pour un projet perso on s'en fiche sinon avec un peu de stratégie de branches, ça limite la casse. Mais ça reste franchement frustrant.

Et le pire, c’est qu’il n’existe pas de solution native pour tester ses workflows en local (on parlera d’act un peu plus tard 👀). Résultat : des commits en série, des allers-retours chronophages… et quelques heures de cris et de larmes dans le processus.

Jour 3 : Un peu de clarté

Comme je l’ai mentionné plus tôt, l’une des erreurs les plus courantes avec GitHub Actions, c’est de vouloir tout entasser dans un seul job d’un seul workflow.

Résultat ? On se prive d’une vision claire de ce qui se passe, et le debug devient vite un enfer.

Le problème du “fourre-tout”

Reprenons mon premier exemple : si le job s’appelle build et qu’il échoue, que s’est-il passé exactement ?

  • Est-ce une dépendance qui a cassé ?
  • Un problème réseau ?
  • Une erreur dans la compilation ?
  • Ou bien un test qui a planté ?

Impossible de le savoir sans plonger dans les logs. Et plus votre workflow grossit, plus ça devient pénible.

La bonne pratique : découper

La solution est simple : découper le workflow en plusieurs jobs clairs et indépendants.

Un schéma générique :

  • test → exécuter la suite de tests
  • build → compiler / packager l’application
  • deploy → livrer le produit

⚠️ Attention aux parallèles

Par défaut, GitHub Actions exécute les jobs en parallèle. Ce qui est souvent très bien pour gagner du temps… sauf quand on veut imposer un ordre logique (ex. : déployer uniquement si les tests et le build sont passés).

Pour gérer ça, il suffit d’utiliser l’attribut needs pour indiquer les dépendances entre jobs. Exemple :

name: CI

on:
	...

jobs:
  test:
	# ...
  build:
    needs: [test]
	# ...
  deploy:
	needs: [build]
	# ...

Avec ça, vos workflows deviennent beaucoup plus lisibles, et vos futurs “vous” (ou vos collègues) vous remercieront le jour où il faudra comprendre pourquoi “ça marche pas”.

Jour 4 : Act, la fausse bonne idée ?

Un jour, au milieu de mes errances, je suis tombé sur ce qui semblait être l’outil qui allait me sauver de mes commits compulsifs et de mes logs interminables.

Son nom : act.
Sa promesse : exécuter vos workflows GitHub Actions directement en local.

Sur les forums et ur YouTube, tout le monde en parlait. Je me suis dit : “ok, c’est peut-être la lumière au bout du tunnel”.

Mes premiers pas avec act

Au début, j’étais sceptique : encore un outil à installer, encore un truc à apprendre. Mais à force de pleurer sur mes workflows cassés, j’ai fini par céder.

Et je dois avouer : la prise en main est agréable. L’outil est bien conçu, avec un affichage clair et lisible. On sent qu’il y a eu du travail derrière.

… et les ennuis commencent

Mais très vite, les premiers bugs sont apparus.

Un de mes workflows échouait en local alors qu’il passait parfaitement sur GitHub. Après quelques recherches, je suis tombé sur cette issue qui nous explique que certaines images utilisées par act n’ont pas les mêmes dépendances que les runners GitHub officiels.

Et là, on comprend le problème :

  • act n’est pas un outil officiel de GitHub.
  • Il simule un runner en utilisant des images Docker gigantesques (plusieurs Go).
  • Résultat : votre machine locale se transforme en centrale nucléaire juste pour lancer quelques scripts.
  • Et pour couronner le tout, certaines actions… ne fonctionnent tout simplement pas.

Mon ressenti

À mon niveau, act s’est révélé être une fausse bonne idée : une belle machine à gaz, qui promet beaucoup mais qui ne tient pas toujours ses promesses.

Jour 5 : Les actions, deuxième fausse bonne idée ?

Bon… on ne va pas se mentir : jusque-là, c’était pas gagné.

Toujours pas moyen de tester facilement mes workflows, donc commits en rafale, temps perdu, et nerfs mis à rude épreuve.

C’est donc ça la vie, Manny ?

Le vrai problème : les actions elles-mêmes

En prenant du recul, j’ai réalisé que la racine du problème n’était pas GitHub Actions en soi… mais les actions.

Celles qu’on nous vend comme la solution miracle sont en réalité parfois des pièges qui nous enferment et rendent le travail encore plus compliqué.

Attention, je ne dis pas qu’elles sont toutes mauvaises !

  • Certaines sont incroyables : par exemple celles qui gèrent les artifacts, super pratiques pour partager des fichiers entre jobs (voire entre workflows).
  • Mais d’autres… franchement, leur valeur ajoutée est faible par rapport aux galères qu’elles amènent. Exemple typique : les actions pour builder et pousser une image Docker. Ça peut paraître sexy, mais en pratique, quelques commandes shell suffisent largement.

Ma solution : reprendre la main

À partir de là, j’ai décidé de réduire au maximum ma dépendance aux actions.

J’ai créé un dépôt dédié où je centralise mes scripts Python pour effectuer des tâches génériques (tag, release, docker, etc.), peu importe la techno du projet.

Ça me donne une base testable, réutilisable et indépendante.

Pour lancer un process, il me suffit de cloner le repo, installer les dépendances… et c’est parti !

Mon dépôt est disponible ici :

GitHub - CharlyGin/pipelines
Contribute to CharlyGin/pipelines development by creating an account on GitHub.

Exemple d’utilisation dans un workflow :

on:
	# ...
	
jobs:
	# ...
	release:
    runs-on: ubuntu-latest
    needs: [test, build, check-version]
    if: needs.check-version.outputs.should-release == 'true'
    permissions:
      contents: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: ${{ github.sha }}
      - name: Install uv
        run: |
          sudo apt update
          sudo apt install -y curl
          sudo curl -LsSf https://astral.sh/uv/install.sh | sh
        shell: bash
      - name: Clone pipelines
        run: |
          cd ~
          git clone https://github.com/CharlyGin/pipelines.git
        shell: bash
      - name: Install dependencies
        run: |
          cd ~/pipelines
          uv sync
        shell: bash
      - name: Run Tag & Release
        run: |
          VERSION=$(jq -r '.version' package.json)
          uv run ~/pipelines/scripts/tag_and_release.py --pre-release --tag $VERSION --app $GITHUB_WORKSPACE --build-dir /dist
        env:
          GH_TOKEN: ${{ secrets.GH_TOKEN }}
        shell: bash

Clarifier les workflows

Autre souci : les petits bouts de scripts shell éparpillés dans les steps.

Ça pollue la lecture des workflows et c’est une nouvelle source de bugs (puisqu’impossible à tester directement).

Ma solution : les déplacer dans des scripts dédiés, par exemple dans un dossier .github/scripts.

Par conséquent, mes workflows ne contiennent plus de logique. Ils se contentent de déclencher des scripts clairs, testables et versionnés. La lisibilité est bien meilleure, et le debug devient plus simple.

Jour 6 : Un peu (beaucoup) d'organisation

Arrivé à ce stade, j’avais fait du ménage dans mes workflows en externalisant mes scripts. Pourtant, un nouveau problème est apparu. Je me retrouvais avec les mêmes scripts appelés plusieurs fois.

Par exemple :

  • une fois pour tester les PRs,
  • une fois pour faire une pré-release,
  • et encore une autre pour la release finale.

Bref, beaucoup de répétition, et une forte envie de factoriser tout ça.

La solution : workflow_call

Heureusement, GitHub Actions propose une fonctionnalité taillée pour ça : l’événement workflow_call.

L’idée est simple :

  • créer un workflow par fonction (ex. : test.yml, build.yml, deploy.yml),
  • leur passer des paramètres (par exemple is_pre-release),
  • et les appeler depuis d’autres workflows.

C’est propre, c’est réutilisable et ça réduit énormément la duplication.

Plus de flexibilité : workflow_dispatch

J’ai ajouté en bonus l’événement workflow_dispatch.

Pourquoi ? Pour pouvoir lancer un workflow manuellement en cas de besoin (ex. : un bug qui bloque la release automatique).

Autre avantage, les workflows s’exécutent dans l’environnement GitHub et donc avec accès aux secrets définis dans le repo, sans que les autres développeurs aient besoin de les stocker (ni d’y avoir accès, d’ailleurs 😅).

Jour 7 : Conclusion

Beaucoup de rage, quelques larmes et un certain nombre de commits inutiles ont été sacrifiés au cours de cette semaine fictive. Mais comme on dit : après la pluie vient le beau temps.

En chemin, j’ai appris :

  • à éviter les jobs fourre-tout,
  • à me méfier des fausses bonnes idées (act ou certaines actions),
  • à privilégier mes propres scripts pour garder la main,
  • et à organiser mes workflows avec workflow_call et workflow_dispatch pour plus de clarté et de souplesse.

GitHub Actions vaut clairement le coup. Cet outil peut sembler compliqué au début, mais il est puissant et flexible. Les actions intégrées sont souvent pratiques, mais il ne faut pas hésiter à reprendre la main quand elles deviennent trop contraignantes. Avec de l’organisation et de la patience, on passe vite de l’enfer au paradis.

Vous pouvez trouver un exemple concret de cette structure dans un template de package Python que j’ai créé :

GitHub - CharlyGin/say_hello: Template Python Package using UV
Template Python Package using UV. Contribute to CharlyGin/say_hello development by creating an account on GitHub.

Sources