Changer le thème

Stratégie de cache GitHub Actions : accélérer le pipeline CI/CD par 5

Easton editorial illustration: fault-isolation scanner

npm install : 3 minutes 15 secondes.

C’était le temps de build CI du projet que j’ai repris l’année dernière. À chaque push, je regardais les logs GitHub Actions tourner, attendant la coche verte. Honnêtement, je passais souvent sur une autre fenêtre — autant attendre.

Après avoir ajouté le cache, le même build se termine en 40 secondes. Environ 5 fois plus rapide.

Ce n’est pas de la magie : c’est une bonne configuration de la stratégie de cache GitHub Actions. Dans cet article, je regroupe les pièges que j’ai rencontrés, les données testées et des modèles de configuration prêts à copier. Si vous attendez aussi vos builds CI, cet article pourrait vous faire gagner pas mal de temps.

1. Concepts clés du mécanisme de cache

Comprenez d’abord comment le cache fonctionne, sinon la configuration devient piégeuse.

Le mécanisme de cache GitHub Actions est simple — trois étapes : recherche → restauration → sauvegarde. Vous définissez une key, GitHub cherche une correspondance parmi tous les caches. Trouvé ? Restauration directe dans votre répertoire de travail. Pas trouvé ? Sauvegarde d’une nouvelle entrée à la fin du job.

Quelques limites strictes à connaître :

LimiteValeur
Plafond de cache par dépôt10 Go
Taille max d’un fichier de cache5 Go (au-delà de 1 Go, les problèmes apparaissent)
Durée de rétentionSuppression après 7 jours sans accès
Limite globale d’upload concurrent5 caches uploadés simultanément max

J’ai vu des gens tomber dans le piège des 10 Go — trop de dépendances, le cache grossit, les nouveaux caches ne peuvent plus être stockés, les anciens sont supprimés, chaque build repart à froid.

Un point souvent confondu : Cache et Artifact ne sont pas la même chose. Le Cache sert le CI, il vise la vitesse ; l’Artifact est pour les humains — artefacts de build, rapports de tests, conservation longue durée. Le Cache a une limite de 10 Go, l’Artifact n’a pas de plafond (mais consomme le stockage du dépôt).

Il y a aussi le Docker Layer Cache, dédié aux builds Docker, avec une logique différente du cache classique — on en parle plus loin.

2. Stratégie de conception des clés de cache

Le succès du cache dépend entièrement de la bonne conception de la key. C’est le cœur de toute stratégie.

Qu’est-ce que hashFiles()

GitHub fournit la fonction intégrée hashFiles(), qui calcule le hash d’un fichier. Souvent utilisée sur package-lock.json ou yarn.lock — dépendances inchangées, hash inchangé, cache hit.

key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}

Cela génère une clé du type npm-Linux-a1b2c3d4e5f6.... Tant que package-lock.json ne change pas, la clé reste identique.

restore-keys : plan de secours

Mais les dépendances finissent par évoluer, d’où restore-keys. C’est un mécanisme de correspondance dégradée :

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      npm-{{ runner.os }}-

Priorité à la key complète. Pas de match ? Recherche d’un ancien cache commençant par npm-Linux-. Ce n’est pas un hit exact, mais la majorité des paquets dans node_modules sont déjà là — installation incrémentale des nouvelles dépendances seulement.

Comparaison de trois modèles de nommage

Après mes tests, voici les trois modèles recommandés :

Mode simple (petits projets) :

key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}

Mode version (plusieurs versions de Node) :

key: {{ runner.os }}-node{{ matrix.node-version }}-{{ hashFiles('**/package-lock.json') }}

Mode multi-chemins (monorepo) :

key: {{ runner.os }}-{{ hashFiles('**/package-lock.json', '**/yarn.lock') }}

Comment vérifier si le cache est hit

actions/cache expose une variable cache-hit :

- uses: actions/cache@v4
  id: cache-npm
  with:
    path: ~/.npm
    key: {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}

- name: Check cache hit
  run: echo "Cache hit - {{ steps.cache-npm.outputs.cache-hit }}"

true = hit exact, false = hit partiel ou miss complet. Vous pouvez conditionner npm ci :

- name: Install dependencies
  if: steps.cache-npm.outputs.cache-hit != 'true'
  run: npm ci

3. Exemples de configuration en production

La théorie est faite, passons au code. Configurations testées en conditions réelles, prêtes à copier.

Cache npm (setup-node recommandé)

setup-node intègre déjà le cache, plus simple que actions/cache manuel :

- uses: actions/setup-node@v4
  with:
    node-version: '20'
    cache: 'npm'  # ou 'yarn', 'pnpm'

Une ligne suffit. Pour cacher d’autres répertoires (ex. node_modules), il faut actions/cache :

- uses: actions/cache@v4
  with:
    path: node_modules
    key: {{ runner.os }}-nm-{{ hashFiles('**/package-lock.json') }}
    restore-keys: {{ runner.os }}-nm-

Mon conseil : privilégier le cache intégré de setup-node, sauf besoin spécifique.

yarn et pnpm

Le répertoire de cache yarn diffère de npm :

- uses: actions/cache@v4
  with:
    path: |
      ~/.yarn/cache
      ~/.yarn/install-state.gz
    key: yarn-{{ runner.os }}-{{ hashFiles('**/yarn.lock') }}

pnpm utilise un store global :

- uses: pnpm/action-setup@v4
  with:
    version: 9

- uses: actions/cache@v4
  with:
    path: ~/.pnpm-store
    key: pnpm-{{ runner.os }}-{{ hashFiles('**/pnpm-lock.yaml') }}

Cache Python/pip

Chemins de cache pour les projets Python :

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: pip-{{ runner.os }}-{{ hashFiles('**/requirements.txt') }}
    restore-keys: pip-{{ runner.os }}-

Docker Layer Cache

Les builds Docker sont les plus longs. BuildKit supporte le backend de cache GitHub Actions :

- uses: docker/setup-buildx-action@v3

- uses: docker/build-push-action@v6
  with:
    context: .
    push: false
    cache-from: type=gha
    cache-to: type=gha,mode=max

type=gha stocke les layers Docker via le service de cache GitHub Actions. En test, un build d’image de 5 minutes descend à environ 1 minute.

Cache des modules Go

- uses: actions/cache@v4
  with:
    path: |
      ~/go/pkg/mod
      ~/.cache/go-build
    key: go-{{ runner.os }}-{{ hashFiles('**/go.sum') }}

Cache Rust Cargo

- uses: actions/cache@v4
  with:
    path: |
      ~/.cargo/registry
      ~/.cargo/git
      target
    key: cargo-{{ runner.os }}-{{ hashFiles('**/Cargo.lock') }}

La compilation Rust est lente — le cache fait gagner beaucoup de temps. Attention : le répertoire target grossit — nettoyage régulier recommandé.

4. Optimisation des performances et bonnes pratiques

Données testées et pièges rencontrés, pour éviter les mêmes erreurs.

Données de référence

D’après le rapport de test RunsOn (mise à jour janvier 2026), avec un cache bien configuré :

OpérationSans cacheAvec cacheGain
npm install3 minutes40 secondes~5×
yarn install2 min 30 s35 secondes~4×
Docker build5 minutes1 minute~5×
pip install45 secondes8 secondes~5×

Taux de hit entre 70 et 90 %, selon la qualité de la stratégie de clés.

Pièges courants

Ne pas cacher node_modules directement

C’est ce que j’ai fait au début — grosse erreur.

# À ne pas faire
path: node_modules

node_modules est spécifique à la plateforme — paquets installés sous Linux, problèmes possibles sous Windows. Mieux vaut cacher le répertoire global (~/.npm) et laisser npm ci assembler.

Cache cross-OS : GNU tar + zstd

Le tar par défaut diffère entre macOS et Windows, échec de restauration possible. Ajoutez :

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}
    enableCrossOsArchive: true

Pollution du cache

Parfois le cache contient des dépendances corrompues et le build échoue en boucle. Solutions :

  1. Suppression manuelle : dépôt GitHub → Actions → Caches → Supprimer
  2. Forcer une nouvelle key : ajouter un préfixe ou un horodatage
key: npm-v2-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}

Checklist des bonnes pratiques

Points clés à vérifier avant configuration :

  1. Privilégier le cache intégré des actions officielles (setup-node, setup-python)
  2. Inclure hashFiles dans la key, sinon les dépendances mises à jour utilisent encore l’ancien cache
  3. Définir restore-keys — la correspondance dégradée sauve la mise
  4. Ne pas cacher node_modules, cacher les répertoires globaux
  5. Nettoyer régulièrement les caches expirés, éviter de dépasser 10 Go

5. Questions fréquentes

Q1 : Pourquoi mon taux de hit est-il faible ?

Cause la plus fréquente : une key qui change trop souvent. Horodatage ou nom de branche dans la key → nouvelle key à chaque push. Solution : utiliser uniquement runner.os et hashFiles, retirer les variables superflues.

Autre cause : hashFiles matche trop de fichiers. Ex. hashFiles('**/*.json') — un changement de config invalide le cache. Limitez à package-lock.json ou yarn.lock.

Q2 : Que faire si l’espace cache dépasse la limite ?

10 Go paraît large, mais un monorepo ou le cache Docker sature vite. Solutions :

  1. Nettoyage régulier : GitHub Actions → Caches, suppression manuelle
  2. Caches séparés : keys distinctes par type de dépendance
  3. Self-hosted runners : pas de limite 10 Go

Q3 : Configuration spéciale pour les self-hosted runners ?

Non, le mécanisme est identique. Avantage : cache local, pas de latence réseau, restauration plus rapide. Inconvénient : pas de nettoyage automatique — script planifié nécessaire.

Q4 : Comment forcer la mise à jour du cache ?

Modifier la key. Ajouter un préfixe de version :

key: npm-v3-{{ runner.os }}-{{ hashFiles('**/package-lock.json') }}

Ou supprimer l’ancien cache pour une régénération complète.

Conclusion

En résumé : bien utiliser le cache, et le CI peut être 5 fois plus rapide.

Un calcul rapide — 2 minutes économisées par build, 10 exécutions par jour, 600 minutes par mois, soit environ 10 heures. De quoi écrire plusieurs articles.

Si vous débutez avec GitHub Actions, commencez par le cache intégré de setup-node, une ligne suffit. Quand vous atteignez un plafond, revenez aux stratégies de clés avancées et au Docker Layer Cache.

Cet article est le 3e de la série GitHub Actions en production. J’ai déjà couvert la mise en place du pipeline CI et les stratégies de déploiement — consultez les articles précédents si ça vous intéresse.

Au prochain push, regardez votre temps de build. De 3 minutes à 40 secondes — à vous de tester.

Configurer le cache GitHub Actions pour accélérer le CI/CD

En configurant le cache GitHub Actions, réduire le temps de build npm install de 3 minutes à 40 secondes

⏱️ Estimated time: 10 min

  1. 1

    Step 1: Choisir une stratégie de cache

    Choisir la stratégie de cache selon le gestionnaire de paquets du projet :

    • Projet npm : privilégier le cache intégré de setup-node
    • Projet yarn/pnpm : configurer les chemins de cache
    • Build Docker : utiliser le backend gha de BuildKit
  2. 2

    Step 2: Concevoir les clés de cache

    Utiliser hashFiles() basé sur le fichier de verrouillage pour générer des clés stables :

    • Modèle de base : {{ runner.os }}-node-{{ hashFiles('**/package-lock.json') }}
    • Ajouter restore-keys comme correspondance de secours
    • Éviter d'inclure un horodatage ou le nom de branche dans la key
  3. 3

    Step 3: Ajouter la configuration de cache

    Ajouter les étapes de cache dans le fichier workflow :

    • npm : utiliser actions/setup-node@v4 avec cache: 'npm'
    • Chemin personnalisé : utiliser actions/cache@v4
    • Docker : définir cache-from et cache-to
  4. 4

    Step 4: Vérifier l'efficacité du cache

    Vérifier si le cache est hit :

    • Consulter la variable de sortie cache-hit (true = hit exact)
    • Comparer les temps de build (réduction attendue de 4 à 5 fois)
    • Vérifier la page Actions → Caches pour confirmer le stockage
  5. 5

    Step 5: Maintenir le cache régulièrement

    Éviter les problèmes de cache :

    • Surveiller l'utilisation de l'espace cache (limite 10 Go)
    • Nettoyer régulièrement les anciens caches
    • En cas de pollution, mettre à jour le préfixe de la key pour forcer une reconstruction

FAQ

Pourquoi mon taux de hit du cache n'est que de 30 % ?
C'est généralement un problème de conception de la key. Vérifiez si vous avez inclus des variables qui changent fréquemment (horodatage, nom de branche) — utilisez uniquement runner.os et hashFiles. Confirmez aussi que le chemin de hashFiles correspond précisément au fichier de verrouillage, sans wildcard trop large.
Que se passe-t-il si le cache dépasse 10 Go ?
GitHub supprime automatiquement les caches les plus anciens pour libérer de l'espace. Recommandations :

• Séparer les caches par type de dépendance (npm, Docker, pip avec des keys distinctes)
• Supprimer manuellement les caches inutiles via Actions → Caches
• Pour un monorepo, envisager des dépôts séparés ou des self-hosted runners
Les branches différentes peuvent-elles partager le cache ?
Par défaut, le cache est partagé uniquement entre la branche courante et la branche par défaut (main/master). Pour un partage inter-branches, retirez le nom de branche de la key et utilisez uniquement le hash basé sur les fichiers. Les restore-keys permettent aussi de matcher des caches d'autres branches.
En quoi le cache diffère-t-il sur un self-hosted runner ?
Le mécanisme est identique, avec deux différences : avantage — le cache est local, restauration plus rapide (sans latence réseau) ; inconvénient — pas de limite 10 Go mais pas de nettoyage automatique, il faut un script planifié.
Un échec de restauration du cache interrompt-il le build ?
Non. Le cache est une optimisation optionnelle ; un échec de restauration n'affecte pas le build. GitHub Actions continue les étapes suivantes, mais télécharge à nouveau les dépendances. Vous verrez « Cache not found for key: xxx » dans les logs, puis un nouveau cache sera sauvegardé pour la prochaine exécution.
Comment savoir si le cache doit être mis à jour ?
Trois cas nécessitent une mise à jour :

• Changement de version des dépendances : hashFiles gère automatiquement, sans intervention manuelle
• Pollution du cache : le build échoue soudainement, supprimer l'ancien cache
• Changement de configuration : ex. mise à niveau de Node, ajouter le numéro de version dans la key

Dans la plupart des cas, une configuration correcte évite toute gestion manuelle.

7 min de lecture · Publié le: 7 avr. 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog