GitHub Actions self-hosted runners : déployer et sécuriser vos runners on-premise

Publié le 16 janvier 2026

DevOps
CI/CD
GitHub

Les GitHub Actions runners hébergés ont des limitations (2000 minutes/mois, pas d'accès ressources internes, packages limitées). Les self-hosted runners offrent contrôle total, performances et coûts réduits. Guide complet de déploiement et sécurisation.

Plan

  • Pourquoi des runners self-hosted ?
  • Installation et configuration
  • Sécurisation : isolation et permissions
  • Auto-scaling avec Kubernetes
  • Gestion des secrets et credentials
  • Monitoring et maintenance
  • Troubleshooting
  • Conclusion

Pourquoi des runners self-hosted ?

Limitations des GitHub-hosted runners

Contraintes techniques :

  • 2000 minutes/mois gratuit (puis $0.008/min)
  • 6h max par job
  • RAM limitée : 7 GB
  • CPU : 2 cores
  • Stockage : 14 GB SSD
  • Pas d'accès réseau interne

Cas d'usage nécessitant self-hosted :

  • Builds lourds (>6h, >7GB RAM, GPU)
  • Accès bases de données internes
  • Artefacts volumineux
  • Environnements spécifiques (architectures ARM, outils custom)
  • Coûts (>10,000 min/mois)

Avantages self-hosted

Performance :

  • Hardware dédié (32+ GB RAM, 16+ cores)
  • SSD local rapide
  • Cache persistent entre builds

Coûts :

  • Gratuit après infra
  • ROI si >250h/mois

Sécurité :

  • Isolation réseau
  • Accès ressources internes
  • Compliance (RGPD, SOC2)

Flexibilité :

  • OS custom
  • Outils pré-installés
  • GPU pour ML

Installation et configuration

Installation simple (VM Linux)

# 1. Créer dossier runner
mkdir -p /opt/actions-runner && cd /opt/actions-runner

# 2. Télécharger runner
RUNNER_VERSION="2.311.0"
curl -o actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
  -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz

# 3. Extraire
tar xzf actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz

# 4. Configurer
# Obtenir token depuis GitHub :
# Repo > Settings > Actions > Runners > New self-hosted runner
./config.sh \
  --url https://github.com/your-org/your-repo \
  --token YOUR_REGISTRATION_TOKEN \
  --name my-runner-1 \
  --labels linux,x64,custom \
  --work _work

# 5. Installer service systemd
sudo ./svc.sh install
sudo ./svc.sh start

# Vérifier
sudo ./svc.sh status

Configuration niveau organisation

Pour runners partagés entre plusieurs repos :

# Token organisation (au lieu de repo)
# GitHub Org > Settings > Actions > Runners > New runner

./config.sh \
  --url https://github.com/your-org \
  --token YOUR_ORG_TOKEN \
  --name org-runner-1 \
  --labels linux,x64,org-shared

Configuration avec Docker

# Dockerfile
FROM ubuntu:22.04

# Installer dépendances
RUN apt-get update && apt-get install -y \
    curl \
    git \
    jq \
    build-essential \
    libssl-dev \
    libffi-dev \
    python3 \
    python3-pip \
    docker.io

# Créer user runner
RUN useradd -m -s /bin/bash runner

# Télécharger runner
WORKDIR /home/runner
RUN curl -o actions-runner-linux-x64.tar.gz \
    -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz \
    && tar xzf actions-runner-linux-x64.tar.gz \
    && chown -R runner:runner /home/runner

USER runner

# Script de démarrage
COPY start.sh /home/runner/start.sh
RUN chmod +x /home/runner/start.sh

ENTRYPOINT ["/home/runner/start.sh"]
# start.sh
#!/bin/bash
./config.sh \
  --url ${GITHUB_URL} \
  --token ${GITHUB_TOKEN} \
  --name ${RUNNER_NAME} \
  --labels ${RUNNER_LABELS} \
  --unattended \
  --replace

./run.sh
# Build et run
docker build -t github-runner .

docker run -d \
  --name runner-1 \
  -e GITHUB_URL="https://github.com/your-org/your-repo" \
  -e GITHUB_TOKEN="YOUR_TOKEN" \
  -e RUNNER_NAME="docker-runner-1" \
  -e RUNNER_LABELS="linux,docker" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  github-runner

Sécurisation : isolation et permissions

Principe : éphémère et isolé

❌ NE JAMAIS :

  • Runner permanent réutilisé
  • Runner avec accès root
  • Secrets hardcodés dans runner
  • Partage runner entre repos publics/privés

✅ TOUJOURS :

  • Runner éphémère (1 job = 1 container détruit)
  • User non-root
  • Secrets via GitHub Secrets
  • Isolation réseau

Runner éphémère avec Docker

# .github/workflows/build.yml
name: Build

on: [push]

jobs:
  build:
    # Utiliser label custom
    runs-on: [self-hosted, linux, ephemeral]
    
    container:
      image: ubuntu:22.04
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Build
        run: |
          apt-get update
          apt-get install -y build-essential
          make build
      
      - name: Test
        run: make test

Isolation réseau

# Firewall : bloquer tout sauf GitHub + ressources internes
ufw default deny outgoing
ufw default deny incoming

# GitHub IPs (récupérer depuis API)
curl https://api.github.com/meta | jq -r '.actions[]' | while read ip; do
    ufw allow out to $ip port 443
done

# Ressources internes (DB, registry)
ufw allow out to 10.0.1.0/24 port 5432  # PostgreSQL
ufw allow out to 10.0.2.0/24 port 5000  # Docker registry

# SSH admin uniquement
ufw allow in from 10.0.0.0/24 to any port 22

ufw enable

User non-root

# Créer user dédié
sudo useradd -m -s /bin/bash runner
sudo usermod -aG docker runner  # Si Docker nécessaire

# Installer runner en tant que runner user
sudo -u runner bash
cd /home/runner
# ... installation runner
# Dockerfile avec user non-root
FROM ubuntu:22.04

RUN useradd -m -u 1000 runner
USER runner

# ... reste installation

Limiter permissions Docker

# /etc/docker/daemon.json
{
  "userns-remap": "runner",
  "no-new-privileges": true
}

sudo systemctl restart docker

Auto-scaling avec Kubernetes

Actions Runner Controller (ARC)

Installation avec Helm :

# Ajouter repo Helm
helm repo add actions-runner-controller \
  https://actions-runner-controller.github.io/actions-runner-controller
helm repo update

# Créer namespace
kubectl create namespace actions-runner-system

# Créer secret avec GitHub token
kubectl create secret generic controller-manager \
  -n actions-runner-system \
  --from-literal=github_token=YOUR_GITHUB_PAT

# Installer controller
helm install actions-runner-controller \
  actions-runner-controller/actions-runner-controller \
  --namespace actions-runner-system \
  --set authSecret.github_token=YOUR_GITHUB_PAT

RunnerDeployment avec auto-scaling

# runner-deployment.yaml
apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
  name: example-runner
  namespace: default
spec:
  replicas: 2
  template:
    spec:
      repository: your-org/your-repo
      labels:
        - kubernetes
        - linux
      
      # Ressources
      resources:
        limits:
          cpu: "2"
          memory: "4Gi"
        requests:
          cpu: "1"
          memory: "2Gi"
      
      # Volumes pour cache
      volumeMounts:
        - name: docker-cache
          mountPath: /var/lib/docker
      
      volumes:
        - name: docker-cache
          emptyDir: {}

---
# HorizontalRunnerAutoscaler
apiVersion: actions.summerwind.dev/v1alpha1
kind: HorizontalRunnerAutoscaler
metadata:
  name: example-runner-autoscaler
spec:
  scaleTargetRef:
    name: example-runner
  
  minReplicas: 1
  maxReplicas: 10
  
  # Scaler selon nombre de jobs en queue
  metrics:
    - type: TotalNumberOfQueuedAndInProgressWorkflowRuns
      repositoryNames:
        - your-org/your-repo
  
  # Scaler selon pourcentage utilisation
  scaleUpThreshold: '0.75'
  scaleDownThreshold: '0.25'
  scaleUpFactor: '2'
  scaleDownFactor: '0.5'
# Appliquer
kubectl apply -f runner-deployment.yaml

# Vérifier
kubectl get runners
kubectl get pods -l app=example-runner

Runner avec DinD (Docker-in-Docker)

apiVersion: actions.summerwind.dev/v1alpha1
kind: RunnerDeployment
metadata:
  name: docker-runner
spec:
  replicas: 3
  template:
    spec:
      repository: your-org/your-repo
      labels:
        - kubernetes
        - dind
      
      # Image avec Docker
      image: summerwind/actions-runner-dind:latest
      
      dockerdWithinRunnerContainer: true
      
      resources:
        limits:
          cpu: "4"
          memory: "8Gi"

Gestion des secrets et credentials

GitHub Secrets (recommandé)

# .github/workflows/deploy.yml
name: Deploy

on: [push]

jobs:
  deploy:
    runs-on: [self-hosted, linux]
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to prod
        env:
          # Secrets injectés par GitHub
          DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
          API_KEY: ${{ secrets.API_KEY }}
        run: |
          ./deploy.sh

Créer secrets :

  • Repo > Settings > Secrets and variables > Actions > New repository secret

HashiCorp Vault (avancé)

jobs:
  deploy:
    runs-on: [self-hosted, linux]
    
    steps:
      - name: Get secrets from Vault
        uses: hashicorp/vault-action@v2
        with:
          url: https://vault.company.com
          token: ${{ secrets.VAULT_TOKEN }}
          secrets: |
            secret/data/prod/db password | DB_PASSWORD ;
            secret/data/prod/api key | API_KEY
      
      - name: Deploy
        env:
          DB_PASSWORD: ${{ env.DB_PASSWORD }}
        run: ./deploy.sh

OIDC avec cloud providers

# Authentification AWS sans credentials
jobs:
  deploy:
    runs-on: [self-hosted, linux]
    
    permissions:
      id-token: write
      contents: read
    
    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
          aws-region: us-east-1
      
      - name: Deploy to S3
        run: aws s3 sync ./dist s3://my-bucket/

Monitoring et maintenance

Prometheus metrics

# ServiceMonitor pour ARC
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: actions-runner-controller
  namespace: actions-runner-system
spec:
  selector:
    matchLabels:
      app: actions-runner-controller
  endpoints:
    - port: metrics
      interval: 30s

Métriques clés :

  • github_runner_jobs_completed_total
  • github_runner_jobs_failed_total
  • github_runner_jobs_duration_seconds
  • github_runner_api_rate_limit_remaining

Alertes Prometheus

# alerts.yaml
groups:
  - name: github-runners
    rules:
      - alert: RunnerJobsFailing
        expr: rate(github_runner_jobs_failed_total[5m]) > 0.1
        for: 10m
        annotations:
          summary: "GitHub runner jobs failing"
      
      - alert: NoRunnersAvailable
        expr: github_runner_available == 0
        for: 5m
        annotations:
          summary: "No GitHub runners available"
      
      - alert: GitHubAPIRateLimitLow
        expr: github_runner_api_rate_limit_remaining < 100
        for: 5m
        annotations:
          summary: "GitHub API rate limit low"

Logs et debug

# Logs runner (systemd)
sudo journalctl -u actions.runner.* -f

# Logs runner (Docker)
docker logs -f runner-1

# Logs ARC (Kubernetes)
kubectl logs -n actions-runner-system \
  -l app=actions-runner-controller -f

# Debug job spécifique
# GitHub > Repo > Actions > Job > Re-run with debug logging

Rotation et updates

#!/bin/bash
# update-runners.sh - Mise à jour rolling runners

RUNNER_VERSION="2.311.0"

# Télécharger nouvelle version
cd /opt/actions-runner
curl -o actions-runner-new.tar.gz \
  -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz

# Pour chaque runner
for i in {1..5}; do
    RUNNER_DIR="/opt/actions-runner/runner-${i}"
    
    # Arrêter runner
    sudo systemctl stop actions.runner.runner-${i}
    
    # Backup
    mv $RUNNER_DIR ${RUNNER_DIR}.bak
    
    # Installer nouvelle version
    mkdir -p $RUNNER_DIR
    tar xzf actions-runner-new.tar.gz -C $RUNNER_DIR
    
    # Restaurer config
    cp ${RUNNER_DIR}.bak/.runner $RUNNER_DIR/
    cp ${RUNNER_DIR}.bak/.credentials $RUNNER_DIR/
    
    # Redémarrer
    sudo systemctl start actions.runner.runner-${i}
    
    # Attendre 30s avant next
    sleep 30
done

echo "Update complete"

Troubleshooting

Problème : Runner ne se connecte pas

# Vérifier service
sudo systemctl status actions.runner.*

# Vérifier logs
sudo journalctl -u actions.runner.* -n 100

# Vérifier connectivité GitHub
curl -v https://api.github.com

# Tester token
curl -H "Authorization: token YOUR_PAT" \
  https://api.github.com/user

Problème : Jobs timeout ou stuck

# Vérifier jobs en cours
ps aux | grep Runner.Listener

# Kill jobs stuck
sudo systemctl restart actions.runner.*

# Vérifier espace disque
df -h /opt/actions-runner/_work

# Nettoyer workspace
rm -rf /opt/actions-runner/_work/*

Problème : Out of memory

# Vérifier utilisation mémoire
free -h
docker stats

# Augmenter swap
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Problème : Docker permission denied

# Vérifier user dans groupe docker
groups runner

# Ajouter au groupe
sudo usermod -aG docker runner

# Redémarrer service
sudo systemctl restart actions.runner.*

Bonnes pratiques

✅ Sécurité

  1. Runners éphémères : 1 job = 1 container/VM détruit
  2. User non-root : jamais de runner avec sudo
  3. Isolation réseau : firewall strict
  4. Secrets externes : GitHub Secrets ou Vault
  5. Audit logs : monitoring actions runner

✅ Performance

  1. Cache persistent : Docker layers, npm/pip cache
  2. Hardware adapté : SSD, RAM suffisante
  3. Pools de runners : labels par type (small/large/gpu)
  4. Auto-scaling : ARC sur Kubernetes
  5. Warm pools : runners pré-warmés

✅ Maintenance

  1. Updates réguliers : runner tous les mois
  2. Monitoring : Prometheus + Grafana
  3. Alertes : jobs failed, no runners available
  4. Backup config : .runner et .credentials
  5. Documentation : runbooks pour troubleshooting

Checklist déploiement

Installation :

  • Runner téléchargé et configuré
  • Service systemd ou Docker running
  • Runner visible dans GitHub UI
  • Labels configurés

Sécurité :

  • User non-root
  • Firewall configuré
  • Pas de secrets hardcodés
  • Runners éphémères si possible
  • Isolation réseau

Performance :

  • RAM >= 8GB
  • CPU >= 4 cores
  • SSD local
  • Cache Docker configuré

Monitoring :

  • Prometheus metrics activés
  • Alertes configurées
  • Logs centralisés
  • Dashboard Grafana

Maintenance :

  • Script update automatique
  • Backup config
  • Rotation runners
  • Documentation troubleshooting

Conclusion

Les self-hosted runners offrent performances, flexibilité et contrôle total pour CI/CD. L'auto-scaling Kubernetes avec ARC permet de gérer des centaines de runners efficacement.

Quand utiliser self-hosted :

  • ✅ Builds > 6h ou >7GB RAM
  • ✅ Accès ressources internes
  • ✅ > 10,000 minutes/mois
  • ✅ Hardware spécifique (GPU, ARM)
  • ✅ Compliance strict

Gains typiques :

  • Coûts : -70-90% si >250h/mois
  • Performance : +50-200% (hardware dédié)
  • Builds parallèles : illimités (vs 20-60 GitHub)
  • Cache : -30-60% build time

Actions prioritaires :

  1. Déployer 2-3 runners test
  2. Configurer labels par type
  3. Implémenter runners éphémères
  4. Activer monitoring Prometheus
  5. Automatiser updates
Besoin d'aide sur ce sujet ?

Notre équipe d'experts est là pour vous accompagner dans vos projets.

Contactez-nous

Articles similaires qui pourraient vous intéresser