Pipelines CI/CD en 2025 : GitHub Actions, GitLab CI et Jenkins
Guide complet des pipelines CI/CD en 2025 : comparaison GitHub Actions, GitLab CI et Jenkins avec meilleures pratiques et exemples concrets.
Pendant deux ans, j’ai déployé à la main. scp, rsync dans le terminal, parfois un tar envoyé sur le serveur. Ça marchait. Mais un vendredi soir, j’ai mergé une régression dans le plugin, déployé sans tester, et cassé la page d’accueil pendant trois heures. Le genre d’incident qui convainc sans discours.
Voilà ce que j’ai mis en place depuis, sur you.arewel.com, un VPS OVH sous Debian 12 Bookworm.
Le déclencheur est simple : quand je publie une release GitHub (ou un workflowdispatch manuel), le workflow :
Pas de branche deploy, pas de tag auto, pas de magie. Un bouton GitHub → une release → un déploiement.
Je n’utilise pas la clé personnelle du développeur pour le deploy. C’est la première chose à mettre en place.
# En local — générer une paire dédiée au déploiement
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/iddeployactions -N ""
La clé publique va dans ~/.ssh/authorizedkeys du compte de deploy sur le serveur (scytale dans mon cas) :
cat ~/.ssh/iddeployactions.pub | ssh scytale@you.arewel.com "cat >> ~/.ssh/authorizedkeys"
La clé privée va dans les secrets GitHub du repo : Settings → Secrets and variables → Actions → New repository secret. Nom du secret : SSHPRIVATEKEY.
Ajouter également :
SSHHOST : you.arewel.comSSHUSER : scytalename: Release & Deploy
on:
release:
types: [published]
workflowdispatch:
inputs:
tag:
description: "Tag à déployer"
required: true
jobs:
build:
runs-on: ubuntu-latest
outputs:
pluginzip: ${{ steps.build.outputs.pluginzip }}
themezip: ${{ steps.build.outputs.themezip }}
steps:
- uses: actions/checkout@v6
- name: Build plugin and theme
id: build
run: |
bash scripts/build-plugin.sh
bash scripts/build-theme.sh
# Récupère les noms de ZIPs générés
PLUGINZIP=$(ls build/yawc-core-plugin-.zip | head -1)
THEMEZIP=$(ls build/youarewelcom-theme-.zip | head -1)
echo "pluginzip=$PLUGINZIP" >> "$GITHUBOUTPUT"
echo "themezip=$THEMEZIP" >> "$GITHUBOUTPUT"
- name: Validate ZIP sizes
run: |
PLUGINSIZE=$(stat -c%s "${{ steps.build.outputs.pluginzip }}")
THEMESIZE=$(stat -c%s "${{ steps.build.outputs.themezip }}")
[ "$PLUGINSIZE" -gt 10000 ] || { echo "Plugin ZIP trop petit"; exit 1; }
[ "$THEMESIZE" -gt 10000 ] || { echo "Theme ZIP trop petit"; exit 1; }
- name: Upload to GitHub Release
uses: softprops/action-gh-release@v3
with:
files: |
${{ steps.build.outputs.pluginzip }}
${{ steps.build.outputs.themezip }}
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Deploy via rsync + SSH
env:
SSHPRIVATEKEY: ${{ secrets.SSHPRIVATEKEY }}
SSHHOST: ${{ secrets.SSHHOST }}
SSHUSER: ${{ secrets.SSHUSER }}
run: |
echo "$SSHPRIVATEKEY" > /tmp/deploykey
chmod 600 /tmp/deploykey
SSHOPTS="-i /tmp/deploykey -o StrictHostKeyChecking=no -p 22"
HTDOCS=/var/www/you.arewel.com/htdocs
rsync -avz --delete -e "ssh $SSHOPTS"
plugins/yawc-core/
"$SSHUSER@$SSHHOST:$HTDOCS/wp-content/plugins/yawc-core/"
rsync -avz --delete -e "ssh $SSHOPTS"
themes/youarewelcom/
"$SSHUSER@$SSHHOST:$HTDOCS/wp-content/themes/youarewelcom/"
ssh $SSHOPTS "$SSHUSER@$SSHHOST"
"wp --path=$HTDOCS cache flush && echo 'Deploy OK — $(date)'"
rm /tmp/deploykey
[tip]Le rsync tourne directement sur le runner GitHub, pas sur le serveur — le serveur n’a pas besoin de clone git. Les fichiers construits localement par le job build sont transférés directement via SSH. Chaque rsync est atomique : si un fichier échoue, le step s’arrête et le déploiement est marqué en échec.[/tip]
La première version du workflow utilisait appleboy/ssh-action pour faire un git pull côté serveur. Ça suppose un clone git sur le serveur — qui n’existait pas. Résultat : step en échec silencieux parce que le répertoire manquait.
La deuxième erreur : wp cache flush dans la commande SSH finale échouait avec Error: This does not seem to be a WordPress installation. WP-CLI cherche wp-config.php dans le répertoire courant, qui est le home du compte SSH. Il faut passer --path= explicitement.
L’autre bug : softprops/action-gh-release@v3 cherche par défaut la release correspondant au tag github.refname. Quand je lance le workflow en workflowdispatch avec un tag manuel, ce tag n’existe pas encore comme release — le step échoue. La solution : ne pas attacher les ZIPs à la release depuis le workflow_dispatch, ou créer la release en amont.
Le job build prend ~45 secondes sur un runner ubuntu-latest. Le job deploy (SSH + rsync + wp cache flush) prend ~20 secondes. Total release → prod : moins de 2 minutes.
GitHub Actions est gratuit pour les repos publics. Pour les repos privés, c’est 2000 minutes/mois incluses dans le plan gratuit. Sur un rythme d’une release par semaine, ça représente ~10 minutes par mois. Pas un sujet.
Je n’aurais pas attendu deux ans. L’investissement de setup est de l’ordre de deux heures. Et depuis que c’est en place, je n’ai plus déployé manuellement une seule fois.
Si je devais refaire : je mettrais en place un test de smoke post-deploy automatique — un curl sur la homepage qui vérifie que le status HTTP est 200 et que X-Cache: HIT est présent dans les headers. Ça m’aurait évité l’incident du vendredi soir.
Article hors série
Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.