Manuelle Deployments sind eine Quelle von Fehlern und Zeitverschwendung. Jeder kennt den Ablauf: SSH einloggen, git pull, Services neustarten, vergessen dass noch pip install nötig war, nochmal einloggen... Irgendwann hatte ich genug davon und habe mir einen einfachen Webhook-Mechanismus gebaut, der das alles automatisch erledigt – ausgelöst durch einen git push.
Das Schöne daran: Es braucht keine externe CI/CD-Plattform, keine GitHub Actions, kein Jenkins. Ein kleines Go-Binary auf dem Server und ein paar Bash-Scripts reichen vollständig aus.
Das Prinzip
GitHub kann bei jedem Push an einen Branch eine HTTP-Anfrage an eine beliebige URL schicken – den sogenannten Webhook. Diese Anfrage enthält Informationen über den Push (Branch, Commit, Repository). Ein kleiner Dienst auf dem Server empfängt diese Anfrage, validiert sie kryptografisch und führt ein Deploy-Script aus.
git push origin main
↓
GitHub sendet POST an https://example.com/webhook/<geheimer-pfad>
↓
webhook-receiver validiert HMAC-SHA256-Signatur
↓
deploy-script.sh wird ausgeführt
↓
App ist aktualisiertDer webhook-receiver Dienst
Als Webhook-Empfänger verwende ich adnanh/webhook – ein schlankes Go-Binary, das als systemd-Dienst läuft. Die Konfiguration erfolgt über eine JSON-Datei:
[
{
"id": "deploy-meinprojekt",
"execute-command": "/etc/webhook-scripts/deploy.sh",
"trigger-rule": {
"and": [
{
"match": {
"type": "payload-hmac-sha256",
"secret": "LANGER_ZUFAELLIGER_TOKEN",
"parameter": { "source": "header", "name": "X-Hub-Signature-256" }
}
},
{
"match": {
"type": "value",
"value": "refs/heads/main",
"parameter": { "source": "payload", "name": "ref" }
}
}
]
}
}
]Zwei Dinge sind hier entscheidend: Die HMAC-Signatur stellt sicher, dass nur echte GitHub-Webhooks den Deploy auslösen – kein fremder Request kann ohne das Secret eine gültige Signatur erzeugen. Der Branch-Filter stellt sicher, dass nur Pushes auf den richtigen Branch den Produktions-Deploy auslösen.
Das Deploy-Script
#!/bin/bash
set -e
cd /var/www/meinprojekt
git pull origin main
venv/bin/pip install -r requirements.txt --quiet
DJANGO_STATIC_ROOT=/var/lib/meinprojekt/staticfiles venv/bin/python manage.py collectstatic --noinput
chown -R meinprojekt:meinprojekt /var/lib/meinprojekt/staticfiles/
systemctl restart meinprojekt
echo "[$(date)] Deploy erfolgreich" >> /var/log/webhook-deploy.logsudo ohne Passwort – aber sicher
Der Webhook-Dienst läuft unter einem eigenen unprivilegierten User. Er braucht aber Rechte, um systemd-Dienste neuzustarten. Dafür reicht eine gezielte sudo-Regel – kein NOPASSWD: ALL, nur die spezifischen Scripts:
security.sudo.extraRules = [{
users = [ "webhook" ];
commands = map (s: { command = s; options = [ "NOPASSWD" ]; }) [
"/etc/webhook-scripts/deploy-app1.sh"
"/etc/webhook-scripts/deploy-app2.sh"
];
}];Ergebnis
Seit ich dieses Setup betreibe, besteht ein Deployment aus einem einzigen Befehl: git push. Durchschnittlich 15 Sekunden später ist die App auf dem neuesten Stand. Das Setup skaliert problemlos auf mehrere Apps – jede bekommt ein eigenes Deploy-Script und einen eigenen Webhook-Hook. Auf meinem Server deployen sich so aktuell sechs verschiedene Apps vollautomatisch.