Was ist ein Webhook und warum自动化?

Ein Webhook ist ein HTTP-Callback — ein POST-Request, den ein externer Service an deinen Server sendet, wenn ein bestimmtes Ereignis eintritt. GitHub sendet einen Webhook nach jedem Push, Stripe nach jeder Zahlung, Travis CI nach jedem Build. Dein Server empfängt den Request und reagiert darauf.

Ohne Webhooks: dupushst Code, loggst dich per SSH ein, ziehst den neuen Stand, startest den Server neu. Mit Webhooks: du pushst Code, GitHub benachrichtigt deinen Server, dein Server pullt, baut, deployed — vollautomatisch, in Sekunden.

Das Ziel ist nicht nur Bequemlichkeit — es ist Consistenz und Schnelligkeit. Manuell deployen heisst: unterschiedliche Personen machen es unterschiedlich, zu unterschiedlichen Zeiten, mit unterschiedlicher Sorgfalt. Automatisiert deployen heisst: der gleiche流程 immer, präzise dokumentiert, auditable.

Webhook ≠ Cron-Job

Ein Cron-Job läuft zeitbasiert (z.B. "jeden Tag um 3 Uhr"). Ein Webhook läuft ereignisbasiert ("wenn etwas passiert"). Webhooks sind das richtige Werkzeug für: Push-to-Deploy, Payment-Benachrichtigungen, externe Monitoring-Alerts, Third-Party-Integrationen. Cron-Jobs sind richtig für: regelmässige Backups, SEO-Sitemap-Generierung, Reporting.

Eigenen Webhook-Endpoint bauen

Der einfachste Weg: ein Express.js-Endpoint, der GitHub-Push-Events verarbeitet. Der Endpoint muss:

// routes/webhook.js — Webhook-Endpoint für GitHub/arbitrary webhooks
const express = require('express');
const router = express.Router();
const crypto = require('crypto');
const { exec } = require('child_process');
const fs = require('fs');

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const DEPLOY_SCRIPT = '/opt/deploy.sh';
const LOG_FILE = '/var/log/webhook-deploy.log';

function log(msg) {
  const line = `[${new Date().toISOString()}] ${msg}\n`;
  fs.appendFileSync(LOG_FILE, line);
  console.log(line.trim());
}

function verifyGitHubSignature(payload, signature) {
  if (!WEBHOOK_SECRET) return true; // Dev mode
  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  const digest = 'sha256=' + hmac.update(payload).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));
}

router.post('/deploy', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-hub-signature-256'] || req.headers['x-gitlab-token'];
  const event = req.headers['x-github-event'] || req.headers['x-gitlab-event'];

  // 1. Verify signature
  if (signature && !verifyGitHubSignature(req.body, signature)) {
    log(`REJECT: Invalid signature from ${req.ip}`);
    return res.status(403).json({ error: 'Invalid signature' });
  }

  // 2. Nur Push-Events automatisch deployen
  if (event === 'push') {
    const payload = JSON.parse(req.body);
    const branch = payload.ref?.split('/').pop();

    if (branch !== 'main' && branch !== 'master') {
      log(`SKIP: Push to ${branch} — not main/master`);
      return res.json({ status: 'skipped', reason: `Branch ${branch} not deployed` });
    }

    log(`DEPLOY: Push to ${branch} by ${payload.pusher?.name}`);

    // 3. Async deployment starten (nicht im Request-Thread blockieren)
    const deployCmd = `bash ${DEPLOY_SCRIPT} 2>&1 >> ${LOG_FILE} &`;
    exec(deployCmd, (err) => {
      if (err) log(`ERROR starting deploy: ${err.message}`);
    });

    // 4. Sofort antworten, damit GitHub keinen Timeout bekommt
    return res.json({ status: 'deploying', message: 'Deployment gestartet' });
  }

  // Andere Events: nur loggen, nicht deployen
  log(`EVENT: ${event} received (no action)`);
  return res.json({ status: 'received', event });
});

module.exports = router;

Das Deployment NIEMALS im Request-Thread starten

Dein Webhook-Endpoint muss IMMER sofort antworten (innerhalb von ~5 Sekunden). GitHub und andere Services haben Timeouts — wenn der Endpoint länger braucht, interpretiert der Service das als Fehler und wiederholt den Webhook (bis zu 72 Stunden lang, Exponential Backoff). Das Deployment im Hintergrund-Process starten (& am Ende des exec-Aufrufs), damit der Endpoint sofort { status: 'deploying' } zurückgibt.

Das Deployment-Script (deploy.sh)

Das Script, das der Webhook-Endpoint aufruft, ist der Kern des automatisierten Deployments:

#!/bin/bash
# /opt/deploy.sh — Automated deployment script
set -e

LOG="/var/log/deploy-$(date +%Y%m%d-%H%M%S).log"
exec > >(tee -a "$LOG") 2>&1

echo "=== Deployment gestartet: $(date) ==="
cd /opt/app

# 1. Git stash (Safety — sollte nie nötig sein wenn nur main gemergt wird)
git stash || true

# 2. Pull latest
echo "Pulling latest main..."
git pull origin main

# 3. Dependencies
echo "Installing dependencies..."
npm ci --production

# 4. Database migrations
echo "Running migrations..."
node migrate.js

# 5. Health check vor dem Switch
echo "Building..."
npm run build 2>/dev/null || true

# 6. PM2 Graceful Reload (Zero-Downtime)
echo "Reloading PM2 process..."
pm2 reload app || pm2 restart app

# 7. Final health check
sleep 3
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health)
if [ "$HTTP_STATUS" = "200" ]; then
  echo "=== Deployment erfolgreich (HTTP $HTTP_STATUS) ==="
else
  echo "=== FEHLER: Health-Check fehlgeschlagen (HTTP $HTTP_STATUS) ==="
  echo "Rollback wird eingeleitet..."
  git reset --hard HEAD~1
  pm2 reload app
  exit 1
fi

GitHub Actions: Alternativer Weg ohne Server-Webhook

GitHub Actions ist eine elegante Alternative zum eigenen Webhook-Server. Du brauchst keinen eigenen Endpoint — GitHub Actions läuft in der GitHub-Cloud und deployt direkt auf deinen Server per SSH:

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

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Deploy to Server
        uses: appleboy/ssh-action@v0.1.10
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SERVER_SSH_KEY }}
          port: 22
          script: |
            cd /opt/app
            git pull origin main
            npm ci --production
            node migrate.js
            pm2 reload app
            curl -f http://localhost:3000/health || exit 1

Die secrets.SERVER_* werden in GitHub Settings → Secrets and Variables → Actions definiert. Das ist deutlich sicherer als Credentials in der Repository zu speichern — und du brauchst keinen eigenen Webhook-Server.

Approach Vorteile Nachteile Eignung
Eigener Webhook-Endpoint Vollständige Kontrolle, keine Drittanbieter-Abhängigkeit, flexibel Eigenes Security-Monitoring nötig, Endpoint muss öffentlich erreichbar sein Geheime/sensitive Setups, Self-Hosted GitLab, Custom-Trigger
GitHub Actions Kein eigener Endpoint, Secrets-Management, gute UI, kostenlos für Public Repos GitHub-Abhängigkeit, komplexere Secrets-Config, nicht alle Anbieter unterstützt Open-Source-Projekte, GitHub-Nutzer, schnelle Setups
Render / Vercel Auto-Deploy Null Konfiguration, eingebautes CI/CD, Preview-Deployments Vendor Lock-in, weniger Kontrolle, Platinslimitiert Small Teams, Startups ohne DevOps-Kapazität

Secrets sicher verwalten

Credentials (API-Keys, Datenbank-URLs, Webhook-Secrets) gehorchen denselben Regeln wie Passwörter — und müssen entsprechend geschützt werden. Hier die Mindestanforderungen:

Tools für Secrets-Management: dotenv für lokale Entwicklung, Render/Vercel Environment Variables für produktive Secrets, HashiCorp Vault für komplexe Setups, agenclave für sensitive secrets auf dem Server.

Rollback: Wenn das Deployment schief geht

Ein automatisierter Deploy ohne Rollback-Strategie ist ein Risiko. Wenn ein Deployment fehlschlägt, willst du in Sekunden auf die vorherige Version zurückwechseln können — nicht in 10 Minuten manueller Arbeit.

# /opt/rollback.sh — Schneller Rollback auf vorherigen Commit
#!/bin/bash
set -e

echo "=== Rollback gestartet: $(date) ==="
cd /opt/app

# Letzten funktionierenden Commit aus Log extrahieren
LAST_GOOD=$(git rev-parse HEAD~1)
echo "Rolling back to: $LAST_GOOD"

git reset --hard $LAST_GOOD
npm ci --production
node migrate.js
pm2 reload app

sleep 3
curl -f http://localhost:3000/health && echo "Rollback erfolgreich" || echo "Rollback FEHLGESCHLAGEN"

Deployment erst nach Health-Check als "erfolgreich" markieren

Dein deploy.sh MUSS einen finalen Health-Check machen (GET auf /health) und bei Nicht-200 automatisch rollen. Das verhindert, dass ein fehlerhaftes Deployment als "live" markiert wird. Bei Render/Vercel passiert das automatisch — bei eigenem VPS musst du es selbst einbauen.

Monitoring: Webhook-Deployments überwachen

Ein automatisiertes Deployment ist nur so gut wie sein Monitoring. Ohne Logs und Alerts weisst du nicht, ob deploys erfolgreich sind — bis ein User es dir meldet.

Monitor Was es checkt Setup
Deployment-Log stdout/stderr jedes Deploys /var/log/webhook-deploy.log — täglich rotieren
PM2 Logs App-Ausgabe inkl. Errors pm2 logs --lines 100
Uptime Monitoring Health-Endpoint nach Deployment UptimeRobot / BetterStack (kostenlos für 1 Check/min)
Disk Space Log-Rotation verhindern df -h im Cron, Alert wenn <10% frei
GitHub Actions Run Erfolg/Fehler der CI-Pipeline GitHub Notifications / Slack-Integration

Fazit: Automatisiere, aber nicht blind

Webhook-basierte Deployments eliminieren manuellen Aufwand und Konsistenz-Probleme — aber nur, wenn du Security und Monitoring von Anfang an einbaust. Das Minimum: HMAC-Signatur-Verifikation, Health-Check nach jedem Deployment, automatisierter Rollback.

Für die meisten Projekte: GitHub Actions mit SSH-Deploy auf einen VPS. Es braucht fast keine Konfiguration, Secrets-Management ist integriert, und du hast volle Sichtbarkeit über den Deploy-Status. Nur wenn du eine spezielle Trigger-Logik brauchst (z.B. Deployment nur nach Manual-Review) oder GitHub nicht nutzt, lohnt sich ein eigener Webhook-Endpoint.

VPS für automatisiertes Deployment

Root-Zugang, Git-Deployment, SSH-Zugang — alle Voraussetzungen für automatische Webhook-basierte Deployments.

Zum Hosting-Vergleich