Aller au contenu
CI/CD & Automatisation medium

Runners GitHub-hosted vs Self-hosted

18 min de lecture

Le choix entre runners GitHub-hosted et self-hosted dépend de vos contraintes : performances, sécurité, coût et accès aux ressources internes. Ce guide donne les critères pour trancher.

  • Comparer GitHub-hosted et self-hosted sur les critères qui comptent
  • Identifier les cas d'usage idéaux de chaque type
  • Calculer le seuil de rentabilité d'un runner self-hosted
  • Combiner les deux dans une architecture hybride
  • Migrer progressivement un workflow vers le self-hosted

Avant d'entrer dans le détail, ce tableau pose les sept critères qui opposent les deux types de runners.

CritèreGitHub-hostedSelf-hosted
MaintenanceAucuneÀ votre charge
Démarrage~20-40sInstantané si pré-chaud
EnvironnementPropre à chaque jobPersistant
CoûtMinutes facturées (privé)Infrastructure à payer
RéseauInternet uniquementAccès réseau interne
SécuritéIsolation garantieVotre responsabilité
SpecsFixes (2 vCPU / 8 Go en dépôt privé, 4 vCPU / 16 Go en dépôt public)Personnalisables

Le runner géré par GitHub est le choix par défaut, et il le reste tant qu'aucune contrainte forte ne vous en éloigne. Les trois sous-sections suivantes détaillent les situations où il gagne, la forme que prend un workflow qui l'utilise, et ce que GitHub prend réellement en charge à votre place.

Ces quatre situations partagent un point commun : rien dans le build n'exige de sortir de la machine jetable fournie par GitHub. Le premier cas est le plus tranché, car sur un dépôt public les minutes sont gratuites et illimitées : payer une infrastructure serait absurde. Les trois autres relèvent d'un arbitrage entre le coût des minutes et le coût, souvent sous-estimé, de l'exploitation d'un parc de machines.

  1. Projets open source : gratuit et illimité
  2. Builds standards : Node.js, Python, Java sans dépendances exotiques
  3. Équipe sans ops : pas de maintenance d'infrastructure
  4. Sécurité critique : isolation garantie entre jobs

Un workflow sur runner géré tient dans une seule ligne, runs-on: ubuntu-24.04. Le reste de l'exemple applique le socle de sécurité de la formation : permissions: {} au niveau du workflow puis les droits minimaux par job, actions épinglées par SHA et persist-credentials: false sur le checkout pour que le GITHUB_TOKEN ne reste pas dans la configuration git du runner. Notez ubuntu-24.04 plutôt que ubuntu-latest : l'image derrière latest change sans préavis et casse les builds reproductibles.

permissions: {}
jobs:
test:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm test

Ce que vous achetez avec un runner géré, ce n'est pas de la puissance de calcul : c'est du travail d'exploitation que vous ne faites pas. Les trois blocs ci-dessous chiffrent implicitement ce transfert de charge. Le plus décisif est le second, l'isolation : chaque job démarre sur une machine virtuelle neuve, détruite à la fin, ce qu'un runner persistant ne peut pas offrir sans configuration supplémentaire.

Zéro maintenance :

  • Mises à jour automatiques des logiciels
  • Patches de sécurité appliqués par GitHub
  • Pas de serveurs à gérer

Isolation parfaite :

  • VM neuve à chaque job
  • Pas de risque de contamination entre jobs
  • Secrets inaccessibles entre runs

Logiciels pré-installés :

Le runner auto-hébergé se justifie quand le job a besoin de quelque chose que la machine jetable de GitHub ne peut pas fournir : une route réseau, un composant matériel, ou un volume de minutes qui rend la facturation déraisonnable. En dehors de ces trois motifs, vous héritez d'une charge d'exploitation sans contrepartie.

Les deux premiers motifs sont structurels : aucun réglage de workflow ne les contourne, il faut une machine à vous. Les trois suivants sont économiques ou réglementaires, donc négociables selon vos volumes et votre secteur. Attention au troisième : l'économie sur les minutes n'est réelle qu'au-delà du seuil calculé plus bas, et elle ne compte pas le temps d'astreinte sur les runners eux-mêmes.

  1. Accès réseau interne : bases de données, registries privés
  2. Hardware spécialisé : GPU, ARM, grandes quantités de RAM
  3. Builds longs/fréquents : économies sur les minutes
  4. Cache persistant : éviter de re-télécharger les dépendances
  5. Contraintes légales : données ne devant pas sortir du réseau

Sur un runner auto-hébergé, runs-on ne nomme plus une image mais une liste de labels que le runner doit tous porter. GitHub cherche alors une machine enregistrée qui déclare self-hosted, linux et x64 à la fois ; si aucune ne correspond, le job reste en attente indéfiniment sans message d'erreur explicite. Le reste de l'exemple montre le motif à copier pour un registre interne : l'identifiant et le mot de passe passent par un bloc env: alimenté par des secrets, et --password-stdin évite que le mot de passe apparaisse dans la ligne de commande visible dans les logs.

permissions: {}
jobs:
build:
runs-on: [self-hosted, linux, x64]
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build avec accès au registry interne
env:
IMAGE_TAG: ${{ github.sha }}
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: |
echo "$REGISTRY_PASSWORD" | docker login registry.internal.company.com \
--username "$REGISTRY_USER" --password-stdin
docker build -t "registry.internal.company.com/app:$IMAGE_TAG" .

Ces trois familles de gains n'ont pas la même solidité. Les performances et le cache local sont mesurables dès le premier build. L'accès réseau est un gain qualitatif : il supprime des tunnels et des règles de pare-feu, donc du travail récurrent. Le coût prévisible, lui, mérite une réserve : il devient un avantage seulement au-delà du seuil de rentabilité, et il ne comptabilise ni les mises à jour du runner, ni le temps passé à diagnostiquer un disque plein.

Performances :

  • Pas de cold start si runner pré-chaud
  • Cache local (dépendances, images Docker)
  • Hardware sur-mesure (SSD NVMe, 128 Go RAM)

Accès réseau :

  • Déploiement direct sur Kubernetes interne
  • Tests contre bases de données internes
  • Pas de tunnel ou VPN à configurer

Coût prévisible :

  • Pas de facturation à la minute
  • Rentable pour les gros volumes

Le coût est souvent le facteur décisif. Comparons la facturation à la minute des runners hosted au coût fixe d'une machine self-hosted.

Deux chiffres pilotent la facture : le quota mensuel inclus dans votre plan, et le tarif à la minute au-delà. Les minutes ne sont décomptées que sur les dépôts privés ; sur un dépôt public avec runners standards, la consommation est gratuite. Le tarif dépend aussi du système : une minute Windows coûte près du double d'une minute Linux, une minute macOS environ dix fois plus. Un job Windows lancé par réflexe alors que Linux suffirait multiplie donc la facture sans que personne ne le remarque.

Minutes incluses par mois (dépôts privés) :
- Free : 2 000
- Pro / Team : 3 000
- Enterprise Cloud : 50 000
Prix après quota (runners standards 2 cœurs) :
- Linux x64 : $0.006/min
- Linux arm64 : $0.005/min
- Windows : $0.010/min
- macOS : $0.062/min

Exemple : 10 000 minutes Linux/mois sur un plan Free = 8 000 minutes facturées = $48/mois

Face à ce coût variable, le self-hosted oppose un coût fixe. La comparaison n'est honnête que si vous ramenez la VM à son usage réel : une machine louée au mois est payée 24 h sur 24, y compris la nuit et le week-end où elle ne construit rien. En contrepartie, elle sert plusieurs dépôts à la fois, ce qui déplace le calcul au niveau de l'organisation plutôt que du projet.

VM Linux (4 CPU, 16 Go) chez un cloud provider :
- ~$80-150/mois selon le provider
- Peut servir plusieurs repos

Seuil de rentabilité : ordre de grandeur de 15 000 à 27 000 minutes Linux par mois, en ne comptant que la location de la VM. Refaites le calcul avec vos tarifs réels : le seuil descend fortement dès que des jobs Windows ou macOS entrent dans le total, et il remonte si vous valorisez le temps d'exploitation des runners.

Au-delà du coût, la vitesse sépare nettement les deux options, au démarrage du job comme pendant le build.

Ce délai est le temps qui s'écoule entre le déclenchement du workflow et la première ligne de log utile. Il est facturé sur les runners gérés et il se répète à chaque job, pas à chaque workflow : une matrice de dix jobs paie dix fois le démarrage. Sur un pipeline de vingt jobs courts, ces quelques dizaines de secondes pèsent plus lourd que le temps de compilation lui-même. La colonne « Warm start » n'a pas de valeur côté GitHub-hosted parce que la VM est détruite après chaque job : il n'existe pas d'état chaud à réutiliser.

TypeCold startWarm start
GitHub-hosted20-40sN/A (toujours cold)
Self-hosted0-5s0s (runner pré-chaud)

Le mot « cache » recouvre deux mécanismes très différents selon le runner. Sur GitHub-hosted, actions/cache télécharge une archive depuis le service de cache de GitHub puis la décompresse sur le disque de la VM : le coût est réseau, il croît avec la taille du node_modules. Sur self-hosted, les dépendances sont déjà sur le disque local d'un job à l'autre, la restauration est quasi instantanée. Cette persistance a un revers direct : un job peut hériter d'un artefact laissé par un job précédent, ce qui rend les builds moins reproductibles et ouvre la voie au cache poisoning.

# GitHub-hosted : télécharge le cache à chaque job
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
# Temps de restore : 10-30s selon la taille
# Self-hosted : cache local sur disque
# Temps de restore : < 1s

La meilleure approche est souvent de combiner les deux :

permissions: {}
jobs:
# Tests rapides sur GitHub-hosted
lint:
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: npm run lint
# Build lourd sur self-hosted
build:
needs: lint
runs-on: [self-hosted, linux, docker]
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: docker build -t app .
# Déploiement interne sur self-hosted
deploy:
needs: build
runs-on: [self-hosted, production-network]
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: kubectl apply -f k8s/

Pour trancher rapidement, cochez les cases qui correspondent à votre situation : la colonne qui en récolte le plus l'emporte.

Une seule case cochée ici suffit parfois à trancher : un projet open source n'a aucune raison de payer une infrastructure, et une équipe sans capacité d'exploitation ne tiendra pas un parc de runners dans la durée. Les autres critères se cumulent plutôt qu'ils ne s'imposent.

  • Le projet est open source
  • Les builds durent moins de 10 minutes
  • Pas besoin d'accès réseau interne
  • L'équipe n'a pas de capacité ops
  • L'isolation entre jobs est critique

À l'inverse, les deux critères matériel spécialisé et accès au réseau interne sont bloquants : si l'un est coché, aucune optimisation de workflow ne vous évitera d'héberger vos runners. Les trois autres sont des critères de volume ou de conformité, à repasser au calcul du seuil de rentabilité. N'oubliez pas la contrainte de sécurité rappelée plus haut : cocher ces cases sur un dépôt public ne rend pas le self-hosted acceptable pour autant.

  • Plus de 15 000 minutes/mois
  • Besoin de GPU ou hardware spécialisé
  • Accès au réseau interne requis
  • Builds très longs (> 30 min)
  • Contraintes de localisation des données

Inutile de tout basculer d'un coup. Une migration réussie est progressive : on cible quelques workflows, on teste, puis on étend.

Commencez par les workflows :

  • Les plus longs (économies maximales)
  • Nécessitant un accès réseau interne
  • Les plus fréquents

Un premier runner sur une machine jetable permet de valider le réseau et les dépendances avant d'engager quoi que ce soit. Quatre points méritent votre attention dans la séquence ci-dessous. D'abord, l'empreinte SHA-256 : GitHub publie celle de chaque archive dans les notes de la release, et la vérifier avant d'extraire évite d'exécuter une archive altérée en transit. Ensuite, le jeton d'enregistrement obtenu dans Settings > Actions > Runners > New self-hosted runner : il expire au bout d'une heure et ne sert qu'une fois. Enfin, ./run.sh occupe le terminal ; pour un usage durable, installez le runner en service avec ./svc.sh install puis ./svc.sh start, sous un compte dédié sans privilèges.

Fenêtre de terminal
# Créer le répertoire de travail
mkdir actions-runner && cd actions-runner
# Télécharger l'archive du runner (version épinglée)
curl -O -L https://github.com/actions/runner/releases/download/v2.336.0/actions-runner-linux-x64-2.336.0.tar.gz
# Vérifier l'empreinte publiée dans les notes de la release v2.336.0
echo "04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d actions-runner-linux-x64-2.336.0.tar.gz" | sha256sum --check
# Extraire seulement si la vérification affiche « Réussi » / « OK »
tar xzf ./actions-runner-linux-x64-2.336.0.tar.gz
# Enregistrer le runner (jeton valable 1 heure, à récupérer dans les réglages du dépôt)
./config.sh --url https://github.com/OWNER/REPO --token AAAAA...
# Démarrer en avant-plan pour valider, avant de passer en service
./run.sh

L'empreinte ci-dessus vaut pour la version 2.336.0 en x64. Pour toute autre version ou architecture, reprenez celle publiée sur la page de la release correspondante : ne recyclez jamais une empreinte d'une version antérieure, la vérification échouerait de toute façon.

La bascule se fait workflow par workflow, en gardant l'ancienne valeur de runs-on en commentaire juste au-dessus. Ce détail n'est pas cosmétique : quand le runner auto-hébergé tombe, les jobs restent en file d'attente sans échouer, et le retour en arrière doit tenir en une ligne décommentée. Lancez le premier workflow migré sur une branche de test, comparez sa durée avec les exécutions précédentes, puis étendez.

jobs:
build:
# Phase 1 : tester sur self-hosted
# runs-on: ubuntu-24.04
runs-on: [self-hosted, linux, x64]

Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

6 questions
6 min.
70% requis

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

  • GitHub-hosted = zéro maintenance, isolation garantie, idéal pour 90 % des cas.
  • Self-hosted = vos machines, pour le GPU, le réseau interne et les gros volumes.
  • Le seuil de rentabilité du self-hosted se situe au-delà de 15 000 minutes Linux par mois, à recalculer avec vos tarifs.
  • Une architecture hybride combine les deux : tests rapides hosted, builds lourds self-hosted.
  • Un runner self-hosted sur un dépôt public est un risque majeur, réservez-le aux dépôts privés.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn