Vor zwei Wochen ging ClawHosters live. Heute läuft das Managed Hosting für KI-Agenten mit rund 50 zahlenden Kunden und 25 weiteren im Trial. Alles von Reddit, ohne Marketing-Budget, neben einem regulären 40-Stunden-Job.
Und ich sage dir gleich vorweg: nichts davon lief glatt.
Dieser Post ist kein Werbetext für mein Produkt. Es ist ein technischer Erfahrungsbericht darüber, wie man eine Managed Hosting Plattform für KI-Agenten baut. Mit echtem Code, echten Fehlern und echten Nächten, in denen um 2 Uhr morgens der Telegram-Bot klingelt, weil eine Kundeninstanz in einer Crash-Loop hängt.
Der Stack: Rails 8 Monolith, PostgreSQL, Sidekiq mit 5 Prozessen und 50 Threads, Clockwork für Scheduling, Hetzner Cloud API für die Infrastruktur. Jeder Kunde bekommt seinen eigenen VPS mit OpenClaw in Docker.
Alles auf einem Server. Kein Kubernetes, kein ECS, keine Managed Database. Das ist eine Entscheidung, keine Einschränkung.
Warum Docker (und warum 70% meiner Kopfschmerzen)
Die Entscheidung, OpenClaw in Docker-Containern zu isolieren statt direkt auf dem VPS laufen zu lassen, war bewusst. Und sie war die Quelle von mindestens 70% aller technischen Probleme. Trotzdem würde ich es wieder so machen.
Das Problem ohne Docker: Wenn ein Kunden-Prozess verrückt spielt (und das tut er, dazu gleich mehr), kann er den gesamten Speicher auffressen, die Festplatte füllen, das OS beschädigen. Mit Docker bekomme ich:
Prozess-Isolation. Der OpenClaw-Container kann meine Host-Services nicht anfassen. SSH, Docker Daemon, node_exporter: alles unerreichbar.
Harte Memory-Limits. 3 GB, 6 GB oder 14 GB je nach Tier. OpenClaw trifft diese Limits regelmäßig.
Immer reparierbar. Selbst wenn der Container komplett kaputt ist, kann ich per SSH auf den Host, Logs inspizieren, Configs reparieren, neustarten. Hätte der Kunde den VPS selbst zerschossen, müsste ich von null anfangen.
Config ist bind-mounted. Die openclaw.json liegt auf dem Host-Dateisystem. Ich kann Configs reparieren, ohne den Container überhaupt starten zu müssen.
Aber Docker hat so viele Probleme mitgebracht, dass ich manchmal dachte, ich hätte mich verrannt.
Die Docker-Probleme im Detail
pnpm Symlinks. pnpm erstellt Symlinks in node_modules/.pnpm/, und docker cp weigert sich schlicht, Symlinks zu kopieren. Updates müssen also über tar cf - | docker cp - gestreamt werden. Klingt trivial, hat mich trotzdem Stunden gekostet, weil die Fehlermeldungen maximal kryptisch waren.
mDNS/Bonjour Auto-Discovery. Das Gateway erkennt die Docker-Bridge-IP (172.18.x.x) statt localhost und wirft dann kryptische "gatewayUrl override rejected" Fehler. Lösung: eine Umgebungsvariable, die das Verhalten deaktiviert. Bis ich die gefunden habe, bin ich fast wahnsinnig geworden.
Zombie-Prozesse. Node handelt SIGCHLD nicht ordentlich. Ohne tini als PID 1 stapeln sich Zombie-Prozesse im Container. Das merkst du nicht sofort, nur wenn nach ein paar Tagen plötzlich die Prozess-Tabelle voll ist.
Nginx Host-Header-Validierung. Nginx im Container validiert den Host-Header. Direkter IP-Zugriff gibt 403. Gut für Security, aber es macht Debugging komplizierter, weil Health-Checks den korrekten Host-Header mitschicken müssen.
Container-Recreation zerstört Runtime-State. Das war der größte Brocken. Jedes Update, jedes SSH-Aktivieren, jede Config-Änderung, die normalerweise eine Container-Neuerstellung erfordern würde, bedeutet: alles weg. Installierte Pakete, Runtime-Daten, Gesprächsverläufe. Man kann nicht einfach docker-compose down && docker-compose up machen. Ich muss erst docker commit ausführen, um den Writable Layer zu sichern, dann Änderungen anwenden. Für Config-Änderungen habe ich ein Hot-Reload-System gebaut, das SIGUSR1 an den Prozess sendet, ohne den Container überhaupt anzufassen.
Die Writable-Layer-Strategie
Kunden können Pakete in ihrem Container installieren. apt-get, pip, npm, alles. Diese Änderungen leben in Dockers Writable Layer (OverlayFS). Das gesamte Update- und Wartungssystem ist darauf ausgelegt, diesen Layer zu erhalten.
Ich nutze docker restart, niemals docker-compose down/up. Vor jeder Operation, die den Container neu erstellen könnte, läuft docker commit, um den Writable Layer ins Base Image zu backen. Backup-Images werden nach erfolgreichen Updates gelöscht, um Speicher freizugeben. Die sind jeweils 15 bis 25 GB groß.
Warum keine Volumes? Weil der Kunde potenziell überall im Dateisystem Änderungen macht. Ein Volume für /usr/local/lib und eins für /home/user/.npm und eins für... nein. Der Writable Layer erfasst alles, egal wo.
5-Layer Subdomain-Routing
Jede Kundeninstanz bekommt eine Subdomain wie my-assistant-x7k2.clawhosters.com. Den Traffic vom Browser zum richtigen VPS zu leiten, braucht fünf Schichten. Ja, fünf.
Layer 1: Cloudflare Wildcard DNS
Ein einziger *.clawhosters.com Record zeigt auf meinen Server. Keine DNS-Records pro Instanz. Cloudflare terminiert SSL öffentlich und verbindet sich dann über ein 15-jähriges Origin-Zertifikat mit dem Server.
Layer 2: Nginx Regex Match
Nginx fängt die Subdomain mit einem Regex server_name ab, blockiert reservierte Wörter (www, api, mail, admin) und leitet an Traefik auf Port 8090 weiter. Kritisch hier: proxy_buffering off und proxy_request_buffering off. Warum das so wichtig ist, kommt im SSE-Abschnitt.
Layer 3: Traefik mit Redis-backed Dynamic Routing
Hier wird es interessant. Traefik liest seine Routing-Tabelle aus Redis. Wenn Rails eine Instanz provisioniert, schreibt es die Routing-Regeln atomar in einem Redis MULTI Block:
traefik/http/routers/<subdomain>/rule = "Host(`<subdomain>.clawhosters.com`)"
traefik/http/services/<subdomain>/loadbalancer/servers/0/url = "http://<vps-ip>:8080"
Dazu registriert es pro Instanz eine bcrypt-gehashte Basic-Auth-Middleware. Traefik nimmt Änderungen sofort über Keyspace Notifications auf. Kein Neustart nötig.
Layer 4: VPS-seitiges Nginx (im Docker)
Auf dem Kunden-VPS läuft Nginx als Sidecar-Container auf Port 8080. Es akzeptiert nur den korrekten Host-Header und proxied an OpenClaw auf internem Port 18789. Alles andere bekommt ein 403 mit "Access denied. Use your subdomain." Letzte Verteidigungslinie gegen direkten IP-Zugriff.
Layer 5: Hetzner Firewall + fail2ban
Produktionsinstanzen bekommen eine Hetzner Cloud Firewall bei der Erstellung. Sie blockiert alles außer 8080, 9100, 22 und 9993/udp für ZeroTier. Die Firewall-Regeln lassen eingehende Verbindungen nur von der IP meines Produktionsservers zu, sodass Kunden-VPS-Instanzen nicht direkt aus dem öffentlichen Internet erreichbar sind. fail2ban ist im Snapshot vorkonfiguriert für SSH-Brute-Force-Schutz.
Self-Healing
Ein Sync-Service läuft alle 10 Minuten und fügt fehlende Routen hinzu oder entfernt verwaiste. Ein Health-Service läuft alle 5 Minuten und macht echte HTTP-Requests durch Traefik mit dem korrekten Host-Header, um End-to-End zu verifizieren, dass das Routing funktioniert. Wenn Traefiks Redis-Subscription nach einem Redis-Neustart abbricht (passiert), startet er den Traefik-Service automatisch neu.
Der LLM-Proxy: SSE-Streaming und warum Nginx alles kaputt macht
Kunden können unser verwaltetes LLM nutzen statt einen eigenen API-Key mitzubringen. Ihr OpenClaw zeigt auf api.clawhosters.com/v1, das eine OpenAI-kompatible Completions API bereitstellt. Dasselbe Grundprinzip nutze ich auch bei individuellen LLM-Workflow-Projekten.
Auth per Source-IP
Kein Token-Management, keine API-Keys zum Rotieren. Jeder VPS hat eine eindeutige Hetzner-IPv4 (unique Index in der DB). Wenn ein Request reinkommt, schauen wir nach, welche Instanz diese IP besitzt. IPv6 nutzt PostgreSQLs CIDR-Containment-Operator, weil Hetzner /64-Blöcke zuweist. Die OpenClaw-Config hat nur ein Dummy-apiKey-Feld, weil der Client sich weigert, Requests ohne eins zu senden.
Die drei Streaming-Albträume
1. TCP Chunk Fragmentation. SSE-Events sind durch \n\n getrennt. Aber HTTP-Chunks von Upstream-Providern sind rohe TCP-Segmente. Ein einzelner Chunk kann ein halbes SSE-Event enthalten, oder drei zusammengeklebte. Ich musste einen Re-Framing-Buffer bauen, der Chunks akkumuliert, an \n\n-Grenzen aufteilt und nur vollständige Events weiterleitet. Klingt einfach. Hat viel zu lange gedauert, alle Edge Cases zu finden.
2. Nginx Buffering killt SSE. Das ist ein bekanntes Problem, das Dutzende Projekte trifft. Aber in einem Multi-Layer-Stack wird es richtig hässlich. Zwei Nginx-Schichten (Hauptserver + Traefiks Upstream-Pfad) bedeuten zwei Stellen, an denen Buffering die gesamte Response akkumulieren kann, bevor sie weitergeleitet wird. Ohne Fix hängt der Client 30 Sekunden und bekommt dann alles auf einmal. "Streaming" nur dem Namen nach.
Wie dieser nginx-SSE-Guide erklärt, braucht man proxy_buffering off, proxy_cache off, proxy_http_version 1.1, chunked_transfer_encoding off UND X-Accel-Buffering: no als Response-Header von Rails. Alle davon. Nicht nur eins.
Ich hatte den Response-Header vergessen und Stunden damit verbracht zu debuggen, warum Streaming lokal funktionierte, aber nicht in Produktion.
# nginx config für SSE streaming
location /v1/ {
proxy_pass http://upstream;
proxy_buffering off;
proxy_cache off;
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_set_header Connection '';
proxy_set_header X-Accel-Buffering no;
}
# Rails Controller - Response Headers für SSE
response.headers['Content-Type'] = 'text/event-stream'
response.headers['Cache-Control'] = 'no-cache'
response.headers['X-Accel-Buffering'] = 'no'
response.headers['Transfer-Encoding'] = 'chunked'
3. Usage-Billing mit Streaming. Provider senden Token-Counts erst im allerletzten SSE-Chunk. Aber Rails ist mitten im Stream, du kannst nicht die gesamte Response im Speicher halten (das würde den Zweck von Streaming zunichtemachen). Lösung: ein Ring-Buffer der letzten 4 KB SSE-Daten. Nach Streamende scanne ich den Buffer nach dem Usage-JSON. Der ensure-Block schließt auch die Upstream-HTTP-Verbindung. Offene Connections stapeln sich schnell, das habe ich auf die harte Tour gelernt.
Bonus-Problem: Manche Provider unterstützen für bestimmte Modelle kein Streaming. Wenn ein Client stream: true sendet, aber der Upstream eine normale JSON-Response zurückgibt, wickelt der Controller sie in eine Fake-SSE-Sequenz ein. So bekommt der Client immer konsistentes SSE, egal was der Provider macht.
Provider-Failover
Routing durch Anthropic, OpenAI, DeepSeek, Google, OpenRouter oder Nvidia, je nach Modell. Bei 5xx vom primären Provider gibt es automatisches Fallback auf OpenRouter mit einem tier-angemessenen Modell. 4xx-Errors werden durchgereicht (das ist das Problem des Aufrufers). Rate-Limiting: 60 req/min allgemein, 10 req/min für Reasoning-Modelle. Redis down? Fail open.
Token-Billing: Das Loch zwischen Observability und Rechnung
Der Streaming-Proxy lief. Token-Daten flossen durch. Ich hatte keine Ahnung, was ich davon in Rechnung stellen sollte.
Wie rechnest du Token-Usage ab, wenn jeder Provider sie anders zählt, anders benennt, und manchmal gar nicht meldet?
Wie Portkeys Token-Tracking-Guide dokumentiert: "Different model providers count, tokenize, and bill tokens differently." Zwei identische Prompts erzeugen unterschiedliche Token-Counts auf GPT-4 vs Claude vs DeepSeek.
Das Provider-Problem
Jeder Provider berichtet Token-Usage anders:
Anthropic sendet usage im letzten SSE-Event mit input_tokens und output_tokens. Relativ zuverlässig. OpenAI schickt es ebenfalls im letzten Chunk, aber das Format unterscheidet sich leicht. DeepSeek? Manchmal fehlt die Usage komplett bei bestimmten Modellen. Google Gemini rechnet in "characters" statt "tokens" in manchen API-Versionen.
Der Ring-Buffer-Ansatz aus dem Streaming-Abschnitt ist die erste Schicht. Wenn die letzte Meile der SSE-Daten das Usage-Objekt enthält, parsen wir es. Wenn nicht, fallen wir auf eine Schätzung zurück, basierend auf der Byte-Größe der Chunks mal einem Provider-spezifischen Faktor.
Observability vs. Rechnung
Es gibt einen Unterschied zwischen "ich weiß ungefähr, wie viele Tokens das waren" und "ich kann das einem Kunden in Rechnung stellen." Für die Observability reicht ein grober Zähler. Für die Rechnung brauchst du:
- Exakte Zuordnung pro Request zu einer Kundeninstanz (über die IP-basierte Auth)
- Provider-spezifische Preise (Claude Sonnet kostet anders als GPT-4o kostet anders als DeepSeek)
- Separierung von Input- und Output-Tokens (Output ist bei den meisten Providern 3 bis 5 Mal teurer)
- Pro-Rating bei Monatswechsel (Kunde bestellt am 15., zahlt er den halben Monat?)
- Reconciliation falls der Ring-Buffer die Usage verpasst hat
Jede LLM-Request wird mit Instanz-ID, Provider, Modell, Input-Tokens, Output-Tokens und exakten Kosten in der Datenbank gespeichert. Jedes Tier enthält ein Token-Kontingent. Die inkludierten Tokens werden zuerst verbraucht. Sobald sie aufgebraucht sind, wird zusätzliche Nutzung pro Claw sofort abgerechnet. Kein Warten auf Monatsende, kein manuelles Abgleichen. Provider-spezifische Preisunterschiede (Claude vs GPT-4 vs DeepSeek) werden über eine Preistabelle normalisiert, die aktualisiert wird, wenn Provider ihre Tarife ändern.
Provisioning: Snapshot-basiert mit Pre-warmed Pool
Alles ist in einen Hetzner-Snapshot vorgebacken. Docker, das OpenClaw-Image (vorgeladen), Playwright/Chromium-Browser, fail2ban, SSH-Hardening. Wenn ein VPS vom Snapshot bootet, regeneriert cloud-init nur SSH-Host-Keys und machine-id, dann startet Docker neu. Ungefähr 3 Minuten bis bereit.
Fly.io hat das gleiche Problem als "Latency Whack-a-Mole" beschrieben: "every time you solve one bottleneck, the next one becomes visible." Die haben es mit Firecracker MicroVMs und getrennten Create/Start-Operationen gelöst. Ich nutze einen Pre-warmed Pool.
Der Pre-warmed Pool
Server werden im Voraus vom Snapshot erstellt, mit einem Platzhalter-Container, der schon läuft. Kunde bestellt, der Code beansprucht atomar einen vorgewärmten VPS, benennt ihn über die Hetzner API um, und deployed die echte Config. Fast sofort.
Der Pool-Manager-Job (läuft alle 10 Minuten) prüft, wie viele freie vorgewärmte VPS verfügbar sind. Fällt die Zahl unter ein konfigurierbares Minimum, bestellt er automatisch neue nach. Die Zielgröße ist dabei seasonally adjustiert: wochentags nachts hält er mehr Pool-Kapazität vor, weil Signups dort häufiger kommen als tagsüber.
Das Deployment selbst ist nur SCP-Config-Dateien + docker-compose up -d + Health-Check-Polling + doctor --fix + SIGUSR1 für Hot-Reload. Keine Pakete installiert, keine Images gepullt. Das ist der Punkt: alles, was zeitaufwändig ist, passiert beim Snapshot-Bau. Zur Deploy-Zeit gibt es nichts mehr zu installieren.
Die IP-Recycling-Bugs
Hetzner recycelt IPs von gelöschten Servern. Das hat zwei Bugs verursacht:
Erstens: Alte SSH-known_hosts-Einträge brachen Verbindungen, selbst mit StrictHostKeyChecking=no. Der Fix war UserKnownHostsFile=/dev/null. Zweitens: Alte IPs in unserer DB konnten auf falsche Server zeigen. Fix: den Hetzner-Metadata-Service von innerhalb des VPS abfragen, bevor wir SSH vertrauen.
Der zweite Bug ist eigentlich der gefährlichere. "Alte IP zeigt auf falschen Server" bedeutet im schlimmsten Fall: wir deployen die Config eines Kunden auf den VPS eines anderen Kunden. Das wäre ein erhebliches Sicherheitsproblem gewesen. Ist nie passiert, weil wir es gefunden haben, bevor es passierte. Aber eng war es.
Der Config-Albtraum
Dieses Thema verdient einen eigenen Abschnitt, weil es der größte operative Schmerz war und immer noch ist.
Das Problem
OpenClaws Config (openclaw.json) ist eine einzelne JSON-Datei mit verschachtelten Keys für LLM-Provider, Messenger-Tokens, Gateway-Einstellungen, Agent-Verhalten, Tool-Berechtigungen. Kunden können sie über OpenClaws CLI bearbeiten. Sie machen Tippfehler, löschen erforderliche Keys, setzen ungültige Werte. Dann crasht ihr OpenClaw in einer Schleife und sie eröffnen ein Support-Ticket.
Crash-Loop Beispiel
OpenClaw v2026.2.23 änderte das Gateway auf --bind lan, das einen spezifischen controlUi-Flag erfordert, der auf true stehen muss. Flag fehlt? Sofortige Crash-Loop. Und OpenClaws eigener doctor --fix Befehl entfernt manchmal Flags, die wir brauchen. Eine Sache reparieren bricht eine andere.
Mein Drei-Schichten-Schutz
Schicht 1: controlUi Flag Protection. Nach jeder Config-Änderung (auch nicht zusammenhängenden) lädt das System die Config neu herunter und verifiziert, dass drei kritische Gateway-Flags vorhanden und true sind. Wenn doctor --fix oder der Kunde sie entfernt hat, werden sie wiederhergestellt, bevor der Reload passiert.
Schicht 2: Automatisches Health-Monitoring + Repair. Jede laufende Instanz wird gepollt. Nach 4 aufeinanderfolgenden Health-Check-Fehlern startet automatisch ein Config-Repair-Service. Er verbindet sich per SSH zur Instanz, liest die letzten 100 Zeilen Container-Logs und pattern-matched Fixes:
Ungültiger
gateway.bindWert: Löscht den bind-Key"Cannot parse configuration": Regeneriert die gesamte Gateway-Sektion aus einem Template
"Unknown configuration key": Führt
doctor --fixmit dem Code der neuen Version aus"Permission denied": chmod-Fix
Nach dem Anwenden der Fixes validiert er auch, dass kritische Felder nicht leer sind, und stellt trustedProxies auf die kanonische Liste zurück.
Schicht 3: Dashboard-Transparenz. Config-Status, Health-Status, Container-Logs, VPS-Metriken (CPU/RAM/Disk/Netzwerk via node_exporter) sind im Kunden-Dashboard sichtbar. Wenn ihr OpenClaw crash-loopt, können sie den Fehler sehen, sehen welcher Config-Key falsch ist, und zumindest versuchen, es selbst zu reparieren, bevor sie ein Ticket eröffnen.
OpenClaw-Updates und die Config-Migration-Registry
OpenClaw veröffentlicht häufig neue Versionen, und sie ändern gerne Config-Defaults auf breaking Art. Ein Key, der optional war, wird verpflichtend. Ein Default ändert sich von permissiv auf restriktiv. Wenn du einfach das Binary updatest, ohne die Config zu migrieren, startet das Gateway nicht.
Tursos Ansatz für Schema-Migrationen über Millionen Datenbanken nutzt eine Pull-basierte Registry: Jeder Client fragt periodisch "Ich bin auf Version X, was habe ich verpasst?" Genau dieses Pattern habe ich für die Config-Versionierung adaptiert.
REGISTRY = [
{ version: "2026.2.22", key: "tools.exec.host", default: "node" },
{ version: "2026.2.23", key: "gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback",
default: true },
{ version: "2026.2.23", key: "browser.ssrfPolicy.dangerouslyAllowPrivateNetwork",
default: true },
{ version: "2026.2.24", key: "agents.defaults.sandbox.docker.dangerouslyAllowContainerNamespaceJoin",
default: true },
{ version: "2026.2.25", key: "agents.defaults.heartbeat.directPolicy",
default: "allow" },
]
Während Updates liest das System die aktuelle Config, wendet nur Migrationen zwischen der alten und neuen Version an, und setzt nur Keys, die fehlen (respektiert Kunden-Anpassungen). Die Version wird in der Config selbst getrackt.
Der Update-Flow: Vorgebautes Tarball hochladen (extrahiert aus dem Upstream-Docker-Image), Dateien via tar in den laufenden Container streamen (nicht docker cp wegen Symlinks), Config-Migrationen ausführen, doctor --fix, docker restart, Health-Check-Polling, Container committen. Backup-Image wird vorher erstellt, danach aufgeräumt.
ZeroTier: Einweg-Netzwerk für lokale LLMs
Das hat mich überrascht. Kunden wollten, dass ihr OpenClaw Geräte in ihrem privaten ZeroTier-Netzwerk erreichen kann. Der Use Case Nummer eins: lokale LLMs. Leute betreiben Ollama oder LM Studio auf ihrem Heimrechner und wollen, dass ihr gehostetes OpenClaw es nutzt, ohne irgendetwas ins öffentliche Internet zu exponieren.
Meine Lösung: ein ZeroTier-Docker-Sidecar.
Ein zweiter Container läuft neben OpenClaw im selben Docker-Bridge-Netzwerk. Er tritt dem ZeroTier-Netzwerk des Kunden bei. Dann nutze ich nsenter, um eine Route in OpenClaws Network-Namespace zu injizieren:
nsenter -t <openclaw_pid> -n ip route add <zt_subnet> via <zt_docker_bridge_ip>
Der ZeroTier-Container macht NAT-Masquerading für ausgehenden Traffic. OpenClaw kann das ZT-Netzwerk erreichen, aber das ZT-Netzwerk kann keine Verbindungen zurück nach OpenClaw initiieren. Keine Return-Route. Einweg by Design.
Das Heimnetzwerk des Kunden bleibt sicher. Ihr OpenClaw kann ihr lokales LLM aufrufen, aber nichts auf der ZT-Seite kann in den Container eindringen. Und der ZeroTier-Container selbst läuft in Docker ohne Zugriff auf den Host-VPS. Selbst wenn das ZeroTier-Netzwerk eines Kunden kompromittiert wird, steckt der Angreifer in einem Container, der den Host nicht erreichen kann.
Das Ganze sind vielleicht 50 Zeilen echte Logik.
Ich hatte mir Wochen von Netzwerk-Schmerzen ausgemalt. Tage mit tcpdump, frustrierte Kunden, Routing-Anomalien, die ich nicht reproduzieren kann. Stattdessen: es hat einfach funktioniert. Die Route wird nach jedem Container-Neustart automatisch neu injiziert.
Das passiert zu selten. Es lohnt sich, kurz darüber nachzudenken, warum: ZeroTier macht genau eine Sache, macht sie im Userspace, und macht sie gut. Das nsenter-Route-Injection-Pattern war die einzige nicht-triviale Entscheidung. Alles andere war Konfiguration.
Recovery und Überwachung
Eine Woche nach Launch verlor ich den Überblick. Fünf Instanzen im Status "deploying", drei davon seit über einer Stunde. Zwei Kunden hatten bereits Tickets. Der Sidekiq-Worker, der den Deploy-Job abarbeiten sollte, war mitten im Run gestorben, und die Instanz wusste es nicht.
Das Monitoring-System, das danach entstand, ist direkt aus diesem Nachmittag gebaut.
Ein Provisioning-Manager-Job läuft alle 5 Sekunden und fängt steckengebliebene Instanzen ab. Wenn etwas im "deploying" Status hängt, aber der VPS auf Port 8080 tatsächlich gesund antwortet, markiert er es als running. Wenn der Deploy-Job gestorben ist, reiht er ihn erneut ein. Instanzen, die 20+ Minuten im "provisioning" Status stecken, werden für manuelle Überprüfung markiert.
Nach 4 aufeinanderfolgenden Health-Fehlern: automatisches Config-Repair. Nach 5: Admin-Alerts an Telegram und E-Mail. Neue Instanzen bekommen eine 10-Minuten-Karenzzeit. Jeder Recovery-Pfad ist durch echte Ausfälle in den letzten Wochen getestet worden.
Dockers eigene Restart-Policies helfen hier nur bedingt. --restart unless-stopped greift nur, wenn der Container-Prozess beendet wird. Ein Container, der läuft, aber deadlocked ist, der den gesamten Speicher frisst, oder keine Verbindung zu seiner LLM-API herstellen kann, wird nicht automatisch neugestartet. Dafür braucht man eine eigene Health-Monitoring-Schicht.
Konkret bei Prometheus: Ich tracke openclaw_health_check_consecutive_failures pro Instanz. Alles über 3 triggert eine PagerDuty-artige Eskalation. Davor dachte ich, ich würde es ohne Metriken merken. Ich habe mich geirrt.
Der Markt ist real
Ich hab ungefähr 50 zahlende Kunden und rund 25 weitere noch im Trial. Nur von Reddit, kein sonstiges Marketing. Ich hab mit vielen von ihnen gesprochen, auch mit denen, die nicht vom Trial konvertiert haben. Das durchgehende Feedback: Es ist für Nicht-Programmierer praktisch unmöglich, OpenClaw reibungslos zu betreiben, oder überhaupt zum Laufen zu bringen. Die Config-Komplexität allein filtert 90% der potenziellen Nutzer heraus.
Ich habe vor 23 Jahren als Script-Kiddy angefangen, bin seit über 10 Jahren professioneller Entwickler. Hab früher ein Crypto Browser Game von Grund auf gebaut und betrieben. Hatte eine große Rocket League Tracking-Seite, RLTracker, die jahrelang Selbstständigkeit finanziert hat. Aber ich bin noch nie auf so viele Probleme rund um eine einzelne Software gestoßen.
OpenClaw selbst ist unglaublich instabil. Config-Formate ändern sich zwischen Minor-Versionen, Defaults kippen ohne Warnung, doctor --fix macht Sachen manchmal schlimmer. Einen zuverlässigen Managed Service darum herum zu bauen, ist ein enormer Job, und genau das ist der Kern einer Managed Hosting Plattform: nicht das Produkt selbst betreiben, sondern es für andere zuverlässig betreibbar machen.
Ja, es gibt Konkurrenz. Mehr als zu Beginn. Aber ich kenne die Probleme jetzt von innen: die Config-Migrationen, die Crash-Loops, das IP-Recycling, das SSE-Buffering. Wer das nie selbst debuggt hat, baut um diese Probleme herum, nicht durch sie hindurch. Das sieht man den Produkten an.
Railway hat sich dafür entschieden, eigene Rechenzentren zu bauen, statt Google Cloud zu nutzen. Das erlaubte ihnen 50% günstigere Preise als Hyperscaler. Ich nutze den gleichen Grundgedanken mit Hetzner direkt statt über AWS oder GCP zu gehen. Den Stack besitzen statt Abstraktionen zu mieten. Der Tradeoff ist Komplexität gegen Kontrolle und Preisflexibilität.
Was ich anders machen würde
Wenn ich morgen nochmal von vorne anfangen würde, ein paar Dinge:
Observability vom ersten Tag an. Ich hab Monitoring nachträglich eingebaut. Was das bedeutete: Als Kunde eins in eine Crash-Loop geriet, hatte ich keine Logs, keine Metriken, nichts. Ich saß vor einem Terminal und riet. Prometheus und node_exporter auf jedem VPS von Anfang an hätten eine Stunde Debugging auf fünf Minuten reduziert.
Config-Validierung vor dem Schreiben, nicht nach dem Crash. Ich validiere jetzt, bevor eine Config-Änderung angewendet wird. Hätte ich das von Anfang an gemacht, wären mir dutzende Support-Tickets erspart geblieben. Jedes einzelne davon ein Kunde, der mich um 23 Uhr anschreibt, weil sein OpenClaw nicht mehr antwortet.
Billing von Anfang an mitdenken. Die Token-Metering-Pipeline nachträglich in einen laufenden Streaming-Proxy einzubauen war schmerzhaft. Der Streaming-Code war auf Performance optimiert, nicht auf Observability. Alles umbauen, ohne den Stream zu unterbrechen, während Kunden aktiv darüber arbeiten. Bitte nicht nachmachen.
Und vielleicht, nur vielleicht, hätte ich nicht alles neben einem Vollzeitjob machen sollen. Die Support-Tickets während der Arbeitszeit... ich sage nur so viel: mein Chef weiß Bescheid und findet es sogar gut.
Wer eine ähnliche Managed Hosting Plattform aufbauen will: die größten Probleme kommen nicht vom Bauen selbst, sondern vom Betreiben danach.