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_totalgithub_runner_jobs_failed_totalgithub_runner_jobs_duration_secondsgithub_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é
- Runners éphémères : 1 job = 1 container/VM détruit
- User non-root : jamais de runner avec sudo
- Isolation réseau : firewall strict
- Secrets externes : GitHub Secrets ou Vault
- Audit logs : monitoring actions runner
✅ Performance
- Cache persistent : Docker layers, npm/pip cache
- Hardware adapté : SSD, RAM suffisante
- Pools de runners : labels par type (small/large/gpu)
- Auto-scaling : ARC sur Kubernetes
- Warm pools : runners pré-warmés
✅ Maintenance
- Updates réguliers : runner tous les mois
- Monitoring : Prometheus + Grafana
- Alertes : jobs failed, no runners available
- Backup config : .runner et .credentials
- 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 :
- Déployer 2-3 runners test
- Configurer labels par type
- Implémenter runners éphémères
- Activer monitoring Prometheus
- Automatiser updates


