Avancé ⏱ 25 min de mise en œuvre Mis à jour le 11 août 2026

Déploiement automatique par webhook : mettre à jour son VPS à chaque git push

Un git push qui met à jour l'app sur le VPS, sans plateforme CI complète. Le webhook déclenche un script. Voici comment le faire, en sécurisant impérativement la signature.

Déployer une application après chaque modification ne nécessite pas toujours une plateforme CI/CD complète. Pour un projet personnel, un prototype ou un petit service, un webhook peut suffire : chaque git push déclenche une requête HTTP vers le VPS, puis un script met à jour l’application.

Cette approche reste légère et facile à comprendre. Elle fonctionne avec GitHub, GitLab ou Gitea, sans agent CI supplémentaire. En revanche, elle ne fournit pas automatiquement de tests, de validation, de stratégie de rollback ou de gestion avancée des environnements.

Les futurs VPS TalCloud pourront servir de cible à ce type de déploiement, avec des serveurs situés en France, du stockage NVMe, une infrastructure conforme aux enjeux du RGPD, une protection Anti-DDoS Netrix et un support humain.

Comment fonctionne un déploiement par webhook ?

Le flux de déploiement tient en quatre étapes :

  1. Un développeur pousse un commit sur la branche main.
  2. GitHub envoie une requête HTTP POST vers une URL du VPS.
  3. Un petit récepteur vérifie la signature cryptographique de la requête.
  4. Si la requête est légitime, il exécute un script de déploiement limité et prédéfini.

Le script peut récupérer les nouveaux commits, télécharger les dernières images Docker et redémarrer les services :

git push
  -> webhook HTTPS
  -> vérification HMAC
  -> deploy.sh
  -> git pull
  -> docker compose up -d

Le contenu envoyé par GitHub décrit le dépôt, la branche et les commits concernés. Ces données servent à valider le contexte, mais ne doivent jamais être transformées directement en commandes shell.

Avertissement de sécurité indispensable

Un endpoint capable de déclencher des commandes sur un serveur constitue une surface d’attaque sensible. Une URL difficile à deviner ne protège pas le service. Un attaquant peut scanner le domaine, retrouver l’endpoint ou rejouer une requête interceptée.

La vérification de signature est donc obligatoire. GitHub signe le corps brut de chaque requête avec un secret partagé et transmet le résultat dans l’en-tête X-Hub-Signature-256. Le récepteur doit recalculer le HMAC SHA-256 avec le même secret et refuser la requête si les signatures ne correspondent pas.

Cette vérification doit être accompagnée de plusieurs protections :

  • Utiliser exclusivement HTTPS.
  • Choisir un secret long, aléatoire et unique.
  • Exécuter un script fixe, sans commande provenant du payload.
  • Limiter les droits de l’utilisateur de déploiement.
  • N’accepter que les événements concernant la branche attendue.
  • Journaliser les tentatives et les déploiements.
  • Ne jamais exposer directement le port interne du récepteur.

Le HMAC authentifie l’émetteur et protège l’intégrité du payload. Il ne remplace pas HTTPS, qui protège aussi la requête pendant son transport.

Quand choisir cette méthode ?

Le webhook direct convient bien à une application personnelle, un environnement de démonstration, un site interne ou une petite stack Docker. Il est pertinent lorsque le déploiement consiste essentiellement à récupérer le code et redémarrer quelques services.

Une véritable chaîne CI/CD devient préférable dès que le projet exige des tests automatiques, plusieurs validations, des artefacts reproductibles, des approbations, des migrations complexes, un déploiement progressif ou un rollback fiable.

Pour un service critique ou une équipe de plusieurs développeurs, préférez GitHub Actions ou GitLab CI. Le webhook direct doit rester un mécanisme simple et maîtrisé, pas une plateforme CI improvisée dans un script toujours plus complexe.

Prérequis sur le VPS

Avant de commencer, préparez les éléments suivants :

  • Un VPS Linux avec Git et Docker Compose installés.
  • Un utilisateur dédié, par exemple deploy, sans droits administrateur inutiles.
  • Le dépôt déjà cloné sur le serveur.
  • Une stack décrite dans un fichier compose.yaml.
  • Un domaine ou sous-domaine, par exemple deploy.example.fr.
  • Un reverse proxy comme Nginx.
  • L’outil webhook installé comme service système.
  • Une clé SSH de lecture si le dépôt est privé.

Le dépôt et les fichiers de l’application peuvent être placés dans /srv/myapp. L’utilisateur deploy doit uniquement pouvoir lire le dépôt, écrire dans les répertoires nécessaires et contrôler les conteneurs concernés.

Écrire le script de déploiement

Créez /srv/myapp/deploy.sh avec un chemin de travail explicite, des commandes fixes et un arrêt immédiat en cas d’erreur :

#!/usr/bin/env bash
set -Eeuo pipefail

APP_DIR="/srv/myapp"
LOG_FILE="/var/log/myapp-deploy.log"
LOCK_FILE="/run/user/$(id -u)/myapp-deploy.lock"

exec 9>"$LOCK_FILE"
flock -n 9 || exit 0

exec >>"$LOG_FILE" 2>&1

echo "[$(date --iso-8601=seconds)] Deployment started"

cd "$APP_DIR"
git fetch origin main
git checkout main
git pull --ff-only origin main

docker compose pull
docker compose up -d --remove-orphans

echo "[$(date --iso-8601=seconds)] Deployment completed"

set -Eeuo pipefail évite de continuer après une erreur. git pull --ff-only refuse une divergence inattendue au lieu de créer un merge sur le serveur. Le verrou flock empêche deux push rapprochés de lancer des déploiements simultanés.

Si l’application doit être reconstruite depuis son Dockerfile, remplacez les deux commandes Docker par :

docker compose build --pull
docker compose up -d --remove-orphans

Le fichier doit appartenir à l’utilisateur de déploiement et ne pas être modifiable par le service web. Le journal doit également être accessible en écriture sans donner de privilèges excessifs.

Configurer un récepteur avec vérification HMAC

L’outil webhook du projet adnanh/webhook peut associer une URL à une commande locale. Sa configuration doit vérifier à la fois la signature GitHub et la branche cible.

Exemple de fichier /etc/webhook/hooks.json :

[
  {
    "id": "deploy",
    "execute-command": "/srv/myapp/deploy.sh",
    "command-working-directory": "/srv/myapp",
    "response-message": "Deployment accepted",
    "trigger-rule": {
      "and": [
        {
          "match": {
            "type": "payload-hmac-sha256",
            "secret": "REMPLACER_PAR_UN_SECRET_LONG_ET_ALEATOIRE",
            "parameter": {
              "source": "header",
              "name": "X-Hub-Signature-256"
            }
          }
        },
        {
          "match": {
            "type": "value",
            "value": "refs/heads/main",
            "parameter": {
              "source": "payload",
              "name": "ref"
            }
          }
        }
      ]
    }
  }
]

Protégez ce fichier, car il contient le secret partagé. Il doit être lisible uniquement par l’utilisateur qui exécute le récepteur. Le même secret devra ensuite être renseigné dans GitHub.

Le service peut écouter uniquement sur l’interface locale :

webhook \
  -hooks /etc/webhook/hooks.json \
  -ip 127.0.0.1 \
  -port 9000 \
  -verbose

L’URL interne devient alors /hooks/deploy. Une requête dont la signature HMAC est absente ou invalide ne doit jamais lancer deploy.sh.

GitLab et Gitea utilisent leurs propres en-têtes et mécanismes de validation selon leur configuration. Il faut adapter la règle au fournisseur choisi, sans supprimer le contrôle cryptographique ou le secret partagé.

Exposer le webhook derrière HTTPS

Le port 9000 ne doit pas être accessible depuis Internet. Nginx reçoit la requête HTTPS et la transmet au service local :

server {
    listen 443 ssl;
    server_name deploy.example.fr;

    ssl_certificate /etc/ssl/example/fullchain.pem;
    ssl_certificate_key /etc/ssl/example/privkey.pem;

    client_max_body_size 2m;

    location = /hooks/deploy {
        proxy_pass http://127.0.0.1:9000/hooks/deploy;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_request_buffering off;
    }
}

Les chemins de certificats sont des exemples. Le pare-feu doit autoriser les ports HTTPS nécessaires tout en bloquant le port 9000 depuis l’extérieur.

Évitez toute transformation du corps de la requête avant sa vérification. Le HMAC est calculé sur les octets exacts du payload : une modification du contenu entraînerait une signature différente.

Configurer le webhook dans GitHub

Dans les paramètres du dépôt GitHub, ajoutez un webhook avec les valeurs suivantes :

  • Payload URL : https://deploy.example.fr/hooks/deploy
  • Content type : application/json
  • Secret : exactement le même secret que dans hooks.json
  • Événement : uniquement les événements push
  • SSL verification : activée

Après l’enregistrement, GitHub affiche l’historique des livraisons et leur code HTTP. Un push sur main doit déclencher le script. Un push sur une autre branche doit être rejeté par la règle ref.

Testez également les cas négatifs : secret incorrect, signature absente, mauvaise branche et accès direct au port interne. Aucun de ces scénarios ne doit provoquer un déploiement.

Renforcer encore la sécurité

Utilisez un secret d’au moins 32 octets générés aléatoirement et changez-le immédiatement s’il apparaît dans un dépôt, un journal ou une capture d’écran. Notre guide sur la gestion des secrets rappelle ces règles.

Le compte deploy ne doit pas disposer d’un accès sudo général. S’il doit contrôler Docker, gardez à l’esprit que l’accès au démon Docker offre des capacités proches de celles de l’administrateur. Une isolation supplémentaire ou un service dédié peut être nécessaire pour les environnements sensibles.

Ne construisez jamais une commande à partir d’un nom de branche, d’un message de commit ou d’un champ du payload. Le récepteur doit uniquement choisir entre refuser la requête et lancer un script connu.

Enfin, surveillez les journaux, limitez la taille des requêtes et envisagez un filtrage par adresses IP GitHub. Ce filtrage reste une protection complémentaire : les plages peuvent évoluer et ne remplacent ni le HMAC ni HTTPS.

Limites et évolution vers une vraie CI/CD

Ce mécanisme automatise efficacement un git pull et un redémarrage Docker, mais il ne garantit pas que le nouveau code fonctionne. Une erreur peut laisser l’application indisponible, sans rollback automatique.

Pour réduire ce risque, ajoutez au minimum une vérification de santé après le redémarrage et conservez une procédure manuelle de retour à la version précédente. Si les exigences augmentent, déplacez les tests et la construction des images vers GitHub Actions ou GitLab CI, puis utilisez le VPS uniquement comme cible de déploiement contrôlée.