ITSkillsCenter
Développement Web

CI GitHub Actions pour monorepo Bun : guide 2026

11 min de lecture

📍 Article principal : Bun monorepo guide complet 2026

Introduction

Une équipe technique de Lomé maintient un monorepo Bun avec quatre apps et trois packages partagés. La CI GitHub Actions naïve qu’ils avaient au démarrage prenait 8 à 12 minutes par PR : cache absent, tests de tous les workspaces relancés à chaque commit, déploiement systématique même quand seul le frontend changeait. Après refactoring de la configuration CI selon les principes décrits dans ce tutoriel, le temps moyen est tombé à 2 minutes 15 secondes, le coût en minutes GitHub Actions a baissé de 75 %, et le déploiement est devenu intelligent — seul ce qui change est redéployé. Ce tutoriel détaille les optimisations applicables à n’importe quel monorepo Bun, l’organisation des jobs en parallèle, le filtrage par chemin pour ne lancer que les tests pertinents, le caching agressif des dépendances et des artefacts, et le pattern de déploiement conditionnel par app. À la fin, vous avez une CI productive qui supporte une équipe distribuée sans devenir un goulot d’étranglement ni faire exploser la facture GitHub.

Prérequis

  • Monorepo Bun fonctionnel sur GitHub
  • Connaissance de base GitHub Actions (workflows YAML, jobs, steps)
  • Compte GitHub avec accès Settings → Actions du dépôt
  • Niveau : intermédiaire — Temps : 1 h 30

Étape 1 — Workflow de base

Le premier workflow installe Bun, restaure le cache des dépendances, exécute les vérifications de typage et les tests. Cette base se complexifiera ensuite avec le filtrage et la parallélisation, mais commencer simple aide à valider que les fondations fonctionnent. Le fichier vit dans .github/workflows/ci.yml et se déclenche sur les pushs et les PRs.

name: CI
on:
  push:
    branches: [main]
  pull_request:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
        with:
          bun-version: 1.2
      - name: Restaurer le cache Bun
        uses: actions/cache@v4
        with:
          path: ~/.bun/install/cache
          key: bun-${{ runner.os }}-${{ hashFiles('bun.lockb') }}
          restore-keys: bun-${{ runner.os }}-
      - name: Installer les dépendances
        run: bun install --frozen-lockfile
      - name: Vérification typage
        run: bun --filter '*' run typecheck
      - name: Tests
        run: bun test

Trois optimisations déjà visibles. Le cache Bun via actions/cache évite de retélécharger les dépendances quand le lockfile n’a pas changé — gain de 30 à 90 secondes. Le flag --frozen-lockfile garantit que la CI échoue si quelqu’un a oublié de commiter une mise à jour de bun.lockb — protection contre les divergences silencieuses. Le filtrage --filter '*' sur le typecheck l’exécute en parallèle sur tous les workspaces, accélérant l’étape sur des machines multi-CPU comme les runners GitHub.

Étape 2 — Filtrage par chemin modifié

Sur un monorepo, beaucoup de PRs ne touchent qu’une partie du code. Lancer tous les tests sur un changement de documentation ou un seul package est gaspillage. GitHub Actions accepte un filtre paths: sur le déclencheur, mais c’est trop grossier : il déclenche ou non le workflow entier. Pour un filtrage fin par job, on utilise l’action tj-actions/changed-files ou un script bash qui détermine quels packages sont impactés.

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      web: ${{ steps.filter.outputs.web }}
      api: ${{ steps.filter.outputs.api }}
      shared: ${{ steps.filter.outputs.shared }}
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            web: ['apps/web/**', 'packages/shared/**']
            api: ['apps/api/**', 'packages/shared/**']
            shared: ['packages/shared/**']

  test-web:
    needs: detect-changes
    if: needs.detect-changes.outputs.web == 'true'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
      - run: bun install --frozen-lockfile
      - run: bun --filter './apps/web' test

Cette structure exécute uniquement les jobs nécessaires. Une PR qui ne touche que apps/api ne lance pas test-web. Une PR qui modifie packages/shared lance les tests de toutes les apps qui en dépendent (cohérent avec l’invalidation transitive). Le résultat sur un monorepo de cinq workspaces : 60 à 80 % des PRs n’exécutent qu’un sous-ensemble de jobs, économisant proportionnellement les minutes CI.

Étape 3 — Jobs parallèles indépendants

GitHub Actions exécute les jobs sans dépendance déclarée en parallèle. On structure donc les vérifications par catégorie indépendante : lint, typecheck, tests unitaires, tests E2E. Chaque catégorie est un job qui s’exécute simultanément, divisant le temps total par le nombre de catégories indépendantes. Sur les runners standards GitHub (2 vCPU, 7 Go RAM), quatre jobs simultanés ne se gênent pas mutuellement.

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v1
      - run: bun install --frozen-lockfile
      - run: bun --filter '*' run lint

  typecheck:
    runs-on: ubuntu-latest
    steps: [ ...même setup, run: bun --filter '*' run typecheck ]

  test-unit:
    runs-on: ubuntu-latest
    steps: [ ...même setup, run: bun test ]

  test-e2e:
    runs-on: ubuntu-latest
    needs: typecheck
    steps: [ ...même setup, build, run: bun --filter './apps/web' run test:e2e ]

Les tests E2E dépendent du typecheck via needs: car ils nécessitent un build cohérent. Les autres jobs n’ont pas de dépendance et démarrent simultanément. Le temps total de la CI devient le maximum des temps individuels, pas leur somme. Sur un monorepo standard, on passe ainsi de 8 minutes en série à 3 minutes en parallèle.

Étape 4 — Cache des builds et artefacts

Au-delà du cache des dépendances, on peut cacher les builds intermédiaires. Si une app a déjà été buildée pour le commit courant (par exemple lors d’un job précédent), on ne la rebuild pas. Le cache porte sur le dossier .svelte-kit, dist, ou tout équivalent du framework utilisé, avec une clé qui inclut le hash des sources concernées.

Pour les artefacts à passer entre jobs (build → déploiement), on utilise actions/upload-artifact et actions/download-artifact. Cette approche convient quand un job de build prépare un livrable que plusieurs jobs de déploiement consomment. Pour les caches purs entre runs, actions/cache reste le mécanisme principal.

Étape 5 — Déploiement conditionnel par app

Le pattern le plus impactant pour réduire le temps et le coût de CI : ne déployer que les apps réellement modifiées. Un changement dans apps/api déclenche le déploiement de l’API, pas celui du frontend. Cette granularité s’appuie sur les outputs du job detect-changes et conditionne chaque job de déploiement.

deploy-api:
  needs: [test-api, detect-changes]
  if: |
    github.ref == 'refs/heads/main' &&
    needs.detect-changes.outputs.api == 'true'
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: oven-sh/setup-bun@v1
    - run: bun install --frozen-lockfile
    - run: bun --filter './apps/api' run build
    - name: Déployer sur Hetzner
      run: |
        rsync -az apps/api/dist/ deploy@${{ secrets.HETZNER_HOST }}:/home/deploy/api/
        ssh deploy@${{ secrets.HETZNER_HOST }} "sudo systemctl restart api.service"

Cette structure est la base. Pour les déploiements zéro-downtime ou multi-environnement (staging vs production), on étend avec des matrices et des environments GitHub. Pour un monorepo qui débute, ce pattern simple suffit largement et évite la sur-ingénierie.

Étape 6 — Auto-merge des Dependabot PRs

Dependabot ouvre automatiquement des PRs pour mettre à jour les dépendances. Sur un monorepo actif, cela peut représenter cinq à quinze PRs par semaine. Configurer l’auto-merge des updates patch et minor (qui ne contiennent pas de breaking changes) garde la stack à jour sans charger l’équipe. Pour les majors, on garde la revue humaine pour évaluer l’impact.

Le workflow d’auto-merge attend que la CI passe avec succès et que la PR soit en mode auto-merge approuvé, puis exécute le merge. Cette automatisation maintient la sécurité (les CVE patchées arrivent en prod en moins de 24 heures) et réduit la dette d’attention sur les mises à jour mineures.

Erreurs fréquentes

Erreur Cause Solution
« Bun: command not found » en CI Action setup-bun mal configurée Vérifier uses: oven-sh/setup-bun@v1 avant tout run: bun
Cache jamais hit Clé incluant un hash dynamique Stabiliser la clé sur hashFiles('bun.lockb')
CI lance tout sur chaque PR Pas de filtrage de chemin Implémenter dorny/paths-filter
Quota minutes Actions épuisé Tests E2E lourds sur chaque PR Lancer E2E uniquement sur main ou tag e2e-needed
Job de déploiement échoue silencieusement Pas de smoke test après déploiement Ajouter curl + assertion sur /healthz
Conflits sur lockfile à chaque update Dependabot Plusieurs PRs simultanées Configurer Dependabot avec open-pull-requests-limit: 5 et grouper

Adaptation au contexte ouest-africain

Trois aspects pratiques. Premièrement, GitHub Actions facture en minutes pour les dépôts privés (2 000 minutes/mois gratuites sur le plan Free). Pour une agence qui pousse 50 PR par mois sur 5 dépôts privés, l’optimisation des temps CI fait la différence entre rester gratuit et payer 4 $ supplémentaires par 1 000 minutes. Sur un an, l’écart entre une CI optimisée (2 min/PR) et naïve (10 min/PR) représente 50 € — somme modeste mais qui paie le café de l’équipe. Deuxièmement, pour les équipes distribuées dans plusieurs villes UEMOA, la CI rapide change la culture : on push avec confiance sans attendre quinze minutes pour valider qu’un changement passe. Cette boucle courte favorise l’expérimentation et la livraison fréquente. Troisièmement, pour les freelances qui livrent à plusieurs clients, ce template de CI réutilisable se factorise dans un fichier YAML générique copié sur chaque nouveau projet, ce qui accélère le démarrage de chaque nouvelle mission.

L’investissement initial de configuration (une à deux journées pour bien optimiser) se rentabilise en quelques semaines via les gains de temps cumulés. Pour les équipes qui démarrent, le conseil pratique est d’investir dans la CI dès le deuxième mois — ni trop tôt (avant d’avoir compris les patterns réels du projet), ni trop tard (avant que la CI ne devienne un goulot d’étranglement).

Tutoriels frères

Pour aller plus loin

FAQ

Faut-il TurboRepo pour la CI ?
Pas pour 95 % des projets. Le caching natif GitHub Actions et le filtrage par chemin couvrent l’essentiel des gains. TurboRepo apporte de la valeur sur des monorepos très complexes (10+ packages, dépendances internes profondes).

Comment tester en CI un service qui dépend d’une base de données ?
Utiliser services: dans le job pour démarrer un container PostgreSQL temporaire, ou SQLite en mémoire pour les tests qui s’y prêtent. Plus rapide et plus déterministe qu’une base partagée.

Self-hosted runners pour économiser ?
Pertinent au-delà de 5 000 minutes/mois consommées. Un runner self-hosted sur Hetzner CX22 (5 €/mois) supporte un trafic important et peut servir plusieurs dépôts. Surveiller la sécurité : un runner self-hosted peut exécuter du code arbitraire fourni par les PR.

Comment debugguer une CI qui échoue uniquement en CI ?
Activer les logs verbeux temporairement (ACTIONS_RUNNER_DEBUG: true), ajouter tmate pour SSH dans le runner pendant l’exécution, ou reproduire localement avec act qui simule GitHub Actions.

Stratégie de coûts détaillée

Le plan Free GitHub Actions pour les dépôts publics est totalement gratuit, sans limite. Pour les dépôts privés, on dispose de 2 000 minutes par mois sur les runners standard (2 vCPU). Au-delà, le coût est de 0,008 $ par minute, soit 4,8 $ pour 600 minutes supplémentaires. Pour mettre ces chiffres en perspective : une CI optimisée à 2 minutes par PR consomme 200 minutes/mois pour 100 PR, donc gratuite. Une CI naïve à 10 minutes par PR consomme 1 000 minutes/mois pour les mêmes 100 PR — encore dans le free tier mais à un seul incident près du seuil. L’optimisation paye en confort de tête plutôt qu’en cash direct pour la majorité des projets.

Pour les équipes ouest-africaines qui veulent maximiser le free tier, deux leviers principaux : optimiser les temps CI (filtrage, parallélisation, caching) et limiter l’usage des runners larges (réservés aux jobs vraiment lents qui justifient les 4 vCPU). Le runner Linux 2-cores reste le sweet spot pour 95 % des cas d’usage. Pour les builds vraiment lourds (Docker images, Playwright complet), on peut envisager des runners self-hosted sur Hetzner — coût marginal de 5 €/mois pour un VPS dédié, utilisable par plusieurs dépôts simultanément.

Sécurité de la chaîne CI

La CI a accès aux secrets de production et peut déployer en automatique — c’est une cible de choix pour les attaques. Trois protections essentielles. Premièrement, restreindre les permissions des workflows : par défaut, GitHub Actions a des permissions très larges. Configurer permissions: {} en début de workflow et n’octroyer que celles nécessaires. Deuxièmement, isoler les secrets de production : les secrets sont stockés au niveau dépôt et accessibles à tous les workflows. Pour la production critique, on utilise les Environments GitHub avec règles d’approbation manuelle avant déploiement. Troisièmement, auditer régulièrement les actions tierces utilisées. Une action compromise (cas connu : 2024 incident tj-actions/changed-files) peut exfiltrer les secrets. Préférer les actions officielles GitHub ou les épingler à un SHA précis (uses: actions/checkout@a4cbc73f...) plutôt qu’à une version (@v4).

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité