G.STANCUTA
Veröffentlicht · 2026 · 05 · 248 Min. Lesezeit

Self-Healing-Server: Code pushen und vergessen

  • infrastructure
  • devops
  • automation
  • self-hosting

Einen Commit pushen und vergessen. Der Server holt, baut neu und startet sich selbst neu. Hier ist das genaue Setup: Supervisor-Daemons, ein nächtlicher Cron, automatisierte Backups und TLS, das sich ohne dich erneuert.

Das Ziel ist einfach: einen Commit pushen, den Laptop schließen und darauf vertrauen, dass die Maschine den Rest erledigt. Keine SSH-Sitzung zum Babysitting. Kein manuelles npm run build um Mitternacht. Der Server holt den neuen Code, baut nur das neu, was sich geändert hat, startet den betroffenen Prozess neu und bedient weiterhin Traffic. Wenn die Maschine nach einem Kernel-Update neu startet, kommen alle Sites von selbst wieder hoch. Das ist der Vertrag.

Das ist keine Magie. Es sind eine Handvoll kleiner, kombinierbarer Teile, von denen jedes eine Aufgabe zuverlässig erledigt. Ein Supervisor pro App. Nginx und der Supervisor beim Booten aktiviert. Ein Cron, der um 3 Uhr morgens läuft und auf Upstream-Änderungen prüft. Automatisierte Datenbank-Backups mit Off-Site-Kopien und einem getesteten Wiederherstellungspfad. TLS-Zertifikate, die sich selbst erneuern. Kombiniert man sie, erhält man einen Server, der sich wie eine verwaltete Plattform verhält, ohne den Preis einer verwalteten Plattform.

Isometrisches Schema von vier Web-Apps hinter nginx, jede verbunden mit einem Supervisor-Daemon und einem nächtlichen Cron-Zyklus
Vier überwachte Apps hinter nginx, gespeist von einer einzigen nächtlichen Update-Schleife.

Die Philosophie: idempotent, beobachtbar, keine manuellen Schritte

Drei Regeln bestimmen jede Entscheidung hier. Idempotent: Ein Skript zweimal auszuführen erzeugt das gleiche Ergebnis wie einmal. Beobachtbar: Jede automatisierte Aktion schreibt eine Log-Zeile, damit man weiß, was passiert ist, ohne graben zu müssen. Keine manuellen Schritte: Wenn die Wiederherstellung nach einem Absturz menschliche Eingaben erfordert, ist das System per Design kaputt.

Das nächtliche Update-Skript veranschaulicht alle drei. Es prüft, ob der lokale HEAD mit dem Remote übereinstimmt. Wenn ja, protokolliert es "keine Änderungen, überspringe" und beendet sich sauber. Wenn sie abweichen, pullt, baut neu und startet neu. Zweimal hintereinander auszuführen, wenn es keine neuen Commits gibt, ist harmlos. Jede Zeile, die es berührt, geht in eine Log-Datei. Niemand muss SSH verwenden.

Supervisor: der Prozess, der deine App nie tot bleiben lässt

Supervisord ist ein Prozess-Supervisor für Linux. Man beschreibt die App in einem INI-Block, und er hält den Prozess auf unbestimmte Zeit am Leben. Absturz? Neustart. OOM-Kill? Neustart. Server neu gestartet? Neustart beim Booten. autostart=true und autorestart=true sind die zwei Zeilen, die eine ganze Kategorie von On-Call-Vorfällen ersetzen.

Auf diesem Server laufen vier Sites: jumpinotech, wegweiserlife, nearyou und luci. Jede bekommt ihren eigenen Supervisor-Programmblock, ihren eigenen Port und ihre eigene Log-Datei. Nginx proxyt von 443 auf diese Ports. Supervisor und Nginx sind beide als systemd-Dienste aktiviert, sodass sie beim Booten vor allem anderen starten.

yaml
[program:site-jumpinotech]
command=/usr/bin/node /var/www/jumpinotech/server.js
directory=/var/www/jumpinotech
user=deploy
autostart=true
autorestart=true
startretries=5
stopwaitsecs=10
stdout_logfile=/var/log/supervisor/jumpinotech.log
stderr_logfile=/var/log/supervisor/jumpinotech.err
environment=NODE_ENV="production",PORT="3001"

Installieren mit sudo apt install supervisor, diesen Block in /etc/supervisor/conf.d/site-jumpinotech.conf ablegen, dann sudo supervisorctl reread && sudo supervisorctl update ausführen. Ein Befehl um den Status aller vier Sites zu prüfen: sudo supervisorctl status. Ein Befehl um ein bestimmtes Log zu verfolgen: sudo supervisorctl tail -f site-jumpinotech.

Der 3-Uhr-Cron: nur neu bauen, was sich geändert hat

Ein Cron-Job feuert jeden Abend um 3 Uhr. Er durchläuft alle vier Site-Verzeichnisse, fetcht vom Remote und vergleicht den lokalen HEAD mit dem Remote-HEAD. Wenn sie übereinstimmen, überspringt er. Wenn sie abweichen, pullt er, führt npm ci und npm run build aus, dann startet er das spezifische Supervisor-Programm neu. Sites, die sich nicht verändert haben, bedienen weiterhin den bestehenden Build.

Das ist wichtig. Wenn man um 23 Uhr einen Fix zu jumpinotech pusht und der Cron um 3 Uhr läuft, wird nur jumpinotech neu gebaut. Die anderen drei Sites bleiben unberührt. Die Build-Zeit ist proportional zu den tatsächlichen Änderungen, nicht zur Anzahl der Apps auf der Maschine.

bash
#!/usr/bin/env bash
# nightly-update.sh -- runs at 03:00 via cron as user deploy
# Rebuilds and restarts only sites whose upstream changed.
set -euo pipefail

SITES="/var/www/jumpinotech /var/www/wegweiserlife /var/www/nearyou /var/www/luci"
LOG="/var/log/nightly-update.log"

echo "--- update run at $(date) ---" >> "$LOG"

for SITE in $SITES; do
  cd "$SITE"
  git fetch origin main --quiet

  LOCAL=$(git rev-parse HEAD)
  REMOTE=$(git rev-parse origin/main)

  if [ "$LOCAL" = "$REMOTE" ]; then
    echo "$SITE: no changes, skipping" >> "$LOG"
    continue
  fi

  echo "$SITE: pulling and rebuilding..." >> "$LOG"
  git pull origin main --quiet
  npm ci --quiet
  npm run build --quiet

  PROG=$(basename "$SITE")
  sudo supervisorctl restart "$PROG" >> "$LOG" 2>&1
  echo "$SITE: restarted OK" >> "$LOG"
done

echo "--- done ---" >> "$LOG"

Zum Crontab des Deploy-Benutzers mit crontab -e und der Zeile 0 3 * * * /home/deploy/scripts/nightly-update.sh hinzufügen. Das Skript benötigt NOPASSWD-sudo für supervisorctl. Das einmal zu /etc/sudoers.d/deploy hinzufügen und man ist fertig.

Datenbank-Backups und ein getestetes Restore

Ein Backup, aus dem noch niemand wiederhergestellt hat, ist kein Backup, es ist eine Sicherheitsdecke. Mein Setup: pg_dump läuft um 2 Uhr morgens, eine Stunde vor dem nächtlichen Update. Der Dump wird mit gzip komprimiert und mit rclone in einen R2-Bucket hochgeladen. Alte Dumps werden nach 14 Tagen sowohl lokal als auch remote gelöscht.

bash
# Database backup -- add to deploy user crontab with: crontab -e
# Runs at 02:00 every night; keeps 14 days of compressed dumps off-site.
0 2 * * * /home/deploy/scripts/db-backup.sh >> /var/log/db-backup.log 2>&1

# db-backup.sh content:
# !/usr/bin/env bash
# set -euo pipefail
# STAMP=$(date +'%Y-%m-%d')
# DUMP="/tmp/db_backup_STAMP.sql.gz"
# pg_dump -U appuser appdb | gzip > "$DUMP"
# rclone copy "$DUMP" r2:my-backups/postgres/
# find /tmp -name 'db_backup_*.sql.gz' -mtime +1 -delete
# rclone delete --min-age 15d r2:my-backups/postgres/
  • pg_dump erzeugt einen reinen SQL-Dump, lesbar und portierbar zwischen Postgres-Versionen
  • gzip halbiert die Dateigröße ungefähr bei den meisten Schema- und Datenmischungen
  • rclone übernimmt den Off-Site-Upload; einmal mit rclone config konfigurieren
  • R2 hat null Egress-Kosten, was unter Druck bei der Wiederherstellung wichtig ist
  • Das Restore vierteljährlich testen: neuesten Dump herunterladen, temporäre Datenbank starten, psql ausführen, Zeilenzählungen überprüfen

TLS, das sich selbst erneuert

Abgelaufene Zertifikate sind peinlich und vermeidbar. Es gibt zwei gute Wege. Wenn die Domain hinter Cloudflare liegt, Full (strict)-Modus mit einem Cloudflare-Ursprungszertifikat verwenden: Es ist 15 Jahre auf dem Ursprung gültig, und Cloudflare verwaltet das browserseitige Zertifikat. Kein Erneuerungsbedarf, je. Wenn certbot bevorzugt wird: installieren, sudo certbot --nginx ausführen, und der installierte systemd-Timer läuft zweimal täglich, um Zertifikate zu erneuern, die in 30 Tagen ablaufen.

Cloudflare wird für alle vier Sites verwendet. Das Ursprungszertifikat liegt in /etc/ssl/cloudflare/origin.pem. Nginx zeigt darauf. Solange Cloudflare davor ist, sehen Browser ein gültiges, von Cloudflare gepflegtes Zertifikat, und das Ursprungszertifikat läuft praktisch nicht ab.

Schema eines KI-Coding-Agenten, der ein AGENTS.md-Runbook liest, um Server-Operationen ohne menschliche Eingabe auszuführen
Ein KI-Agent liest Markdown-Gedächtnis, um Ops-Aufgaben auszuführen, ohne bei jeder Sitzung den Kontext zu wiederholen.

Einem KI-Agenten ein persistentes Ops-Runbook geben

Claude Code wird als Coding- und Ops-Agent für alle diese Projekte genutzt. Das Problem mit KI-Agenten und Infrastruktur ist das Gedächtnis: Jede Sitzung beginnt leer. Der Agent kennt weder die Port-Zuweisungen, noch die Supervisor-Programmnamen, die Wiederherstellungsprozedur oder warum der Cron um 3 Uhr und nicht um 2 Uhr läuft. Man wiederholt sich, oder man bekommt halluzinierte Befehle, die fast der Realität entsprechen.

Die Lösung ist eine eingecheckte AGENTS.md-Datei im Projektstamm. Claude Code liest sie automatisch beim Sitzungsstart über seine CLAUDE.md-Konvention. Die Datei enthält das Ops-Runbook: Port-Tabelle, Supervisor-Namen, Cron-Zeitplan, TLS-Setup, Wiederherstellungsschritte, bekannte Fallstricke. Wenn sich etwas ändert, wird der Agent angewiesen, die Datei zu aktualisieren. Sie wird zur einzigen Quelle der Wahrheit, die Kontext-Window-Resets übersteht.

Das Format ist bewusst knapp. Keine Prosa. Nur Fakten, auf die der Agent direkt handeln kann.

md
# Ops Runbook

## Sites and Ports
| App            | Port | Supervisor name   |
|----------------|------|-------------------|
| jumpinotech    | 3001 | site-jumpinotech  |
| wegweiserlife  | 3002 | site-wegweiserlife|
| nearyou        | 3003 | site-nearyou      |
| luci           | 3004 | site-luci         |

## Nginx
- Config root: /etc/nginx/sites-enabled/
- Reload: sudo nginx -s reload
- All four sites proxied from 443 to the port above.

## Supervisor
- Config dir: /etc/supervisor/conf.d/
- Commands: sudo supervisorctl status | restart <name> | tail <name>

## Cron (deploy user)
- 03:00 nightly: /home/deploy/scripts/nightly-update.sh
- 02:00 nightly: /home/deploy/scripts/db-backup.sh

## TLS
- Provider: Cloudflare (proxied + Full strict mode)
- Fallback: certbot --nginx, timer: systemctl status certbot.timer

## Database Restore
1. Download latest dump from R2 bucket: rclone copy r2:my-backups/postgres/STAMP.sql.gz /tmp/
2. gunzip /tmp/STAMP.sql.gz
3. psql -U appuser appdb < /tmp/STAMP.sql

## Known Gotchas
- nightly-update.sh needs NOPASSWD sudo for supervisorctl (already in /etc/sudoers.d/deploy)
- Cloudflare origin cert lives at /etc/ssl/cloudflare/origin.pem -- do not delete

Mit dieser Datei im Repo öffnet man eine Sitzung und sagt "site-nearyou neu starten und das Log auf Fehler prüfen." Der Agent kennt den Supervisor-Namen, die Befehlssyntax und wo das Log liegt. Er fragt nicht. Er rät nicht. Er handelt. Dasselbe für "zeig mir den Zeitstempel des letzten Backups" oder "auf welchem Port läuft luci." Das Runbook beantwortet diese Fragen, bevor man die Frage zu Ende getippt hat.

Der vollständige Stack, zusammengefasst

Hier ist alles an einem Ort. Vier Next.js-Sites, jede auf ihrem eigenen Port (3001 bis 3004), von supervisord überwacht, von nginx proxyt. Sowohl supervisord als auch nginx sind über systemd beim Booten aktiviert. Ein Cron um 3 Uhr prüft jede Site auf Upstream-Änderungen und baut nur die neu, die sich geändert haben. Ein separater Cron um 2 Uhr dumpt die Postgres-Datenbank, komprimiert sie und schickt sie zu R2. TLS ist Cloudflare davor mit einem Ursprungszertifikat, das auf einer menschlich relevanten Zeitskala nicht abläuft.

Einen Commit pushen. Der Cron holt ihn um 3 Uhr ab. Wenn der Build durchläuft, startet der Supervisor den Prozess neu. Die Site läuft auf dem neuen Code, bevor jemand in Europa aufwacht. Wenn der Build fehlschlägt, läuft der alte Prozess weiter, das Log zeichnet den Fehler auf, und nichts geht dunkel. Das ist es, was Self-Healing wirklich bedeutet: nicht dass Fehler unmöglich sind, sondern dass das System kontrolliert degradiert und sich ohne einen erholt.

"Den Wiederherstellungspfad automatisieren, bevor der Fehler passiert, nicht danach." Ein System, das man um 6 Uhr morgens vom Telefon aus reparieren kann, ist ein System, das einen die Nacht durchschlafen lässt.

Portfolio · Schriftfeld
Gezeichnet von
G. STANCUTA
Disziplin
AI & AUTOMATION
Standort
MORTER · SÜDTIROL
Status
Verfügbar
Sprachen
IT · EN · RO · DE+
Stack
PLOI · HETZNER
Revision
REV 2026.A
2026

© 2026 Gabriel Stancuta · jumpinotech.com — Mit KI entworfen, gebaut, um sich selbst zu betreiben.