INFRASTRUKTUR

Ich habe 3 Monate Trigger.dev selbst gehostet. Das habe ich wirklich daraus gelernt.

Semih AkguelSemih Akguel
1. Juni 202610 Min. Lesezeit

Du hast Trigger.dev gegoogelt, weil du Hintergrund-Jobs brauchst. Vielleicht hast du es schon installiert. Vielleicht läuft es schon in Produktion. Und vielleicht fragst du dich, warum der Server abstürzt, obwohl CPU und RAM noch größtenteils frei sind.

Das ist kein Konfigurationsfehler. Das ist Architektur. Und die drei Monate, in denen ich das auf die harte Tour gelernt habe, bekommst du jetzt gratis.

Wer ich bin — und warum drei Monate zählen

Ich habe nicht einen Blog über Trigger.dev gelesen und zusammengefasst. Ich habe diese Systeme selbst gebaut — angefangen mit reinem Python und reinem JavaScript an der Uni, Automatisierungen für eigene Projekte. Dann Make.com, Zapier und n8n, als die No-Code-Tools reifer wurden. Dann zurück zum Code, als die Grenzen visueller Tools offensichtlich wurden. Maßgeschneiderte Automatisierungssysteme für Kunden, eigene Webhook-Layer, eigene Job-Queues.

Trigger.dev war der logische nächste Schritt. Eine moderne, code-first Plattform für Hintergrund-Jobs mit Durable Execution, Echtzeit-Monitoring und einer sauberen TypeScript-API. Also habe ich getan, was die meisten überspringen: Ich habe es richtig evaluiert, in Produktion, im großen Maßstab, auf einem dedizierten 96-Kern-Hetzner-Server mit 251 GB RAM. Nur so weißt du wirklich, ob ein Tool standhält. Die Doku zu lesen sagt es dir nicht. Ein Wochenend-Prototyp sagt es dir nicht. Dreißigtausend Jobs in einem einzigen Batch sagen es dir.

Was aus diesen drei Monaten herauskam: ein 56-seitiges Disaster-Recovery-Playbook, ein eigener Warm-Start-Broker-Dienst, ein Zombie-Killer, der als systemd-Daemon läuft, und eine klare Antwort auf die einzige Frage, die zählt — die ich dir gegen Ende gebe.

Das ist, was mir jemand an Tag eins hätte in die Hand drücken sollen.

Warum ich das überhaupt brauchte: Cold Email im großen Maßstab

Ich habe keine 96-Kern-Job-Pipeline zum Spaß gebaut. Ich habe sie gebaut, weil Cold Email, das tatsächlich konvertiert, ein Enrichment-Problem ist — und Enrichment im großen Maßstab ist ein Ausführungsproblem.

Eine generische Cold Email wird ignoriert. Eine personalisierte bekommt eine Antwort. Aber echte Personalisierung ist kein Serienbrief-Feld. Für jeden Interessenten heißt das: die Website scrapen, eine zustellbare E-Mail finden und verifizieren, Firmendaten ziehen, den Fit bewerten und eine Eröffnungszeile generieren, die spezifisch genug ist, um zu beweisen, dass wirklich ein Mensch hingesehen hat. Das sind fünf oder mehr Ausführungen pro Interessent, bevor auch nur eine E-Mail rausgeht.

Fahr eine Kampagne an 5.000 Interessenten und du bist bei 25.000+ Ausführungen. Fahr mehrere pro Woche über mehrere Kunden und du bist jenseits von 100.000 Ausführungen pro Tag. Jede einzelne ist I/O-bound — ein API-Call, ein Scrape, ein Lookup — also genau die Last, die einen skalierenden Job-Runner von einem trennt, der bei 200 gleichzeitigen Pods umkippt.

Hier hört die Architekturwahl auf, akademisch zu sein. Wenn deine Enrichment-Pipeline stockt, geht deine Kampagne zu spät oder halb-personalisiert raus — und flache Personalisierung ist der Unterschied zwischen 4 Prozent und 0,4 Prozent Antwortrate. Und bei Managed-Pricing pro Ausführung sind 100.000 Ausführungen pro Tag à 0,05 $ ganze 150.000 $ pro Monat, nur um Interessenten zu recherchieren. Die falsche Infrastruktur kostet dich nicht nur Uptime. Sie deckelt, wie personalisiert und wie groß dein Outbound je sein kann.

Der Stack, den ich dafür betreibe, ist bewusst langweilig: Apify zum Scrapen, Prospeo zum Finden und Verifizieren von E-Mails, Instantly zum Versenden. Der Job-Runner darunter entscheidet, ob das Ganze den Kontakt mit dem Volumen überlebt. Alles Folgende ist die Geschichte davon, das herauszufinden.

Der Crash — und was wirklich dahintersteckt

Dedizierter Hetzner-Server. 96 Kerne, 251 GB RAM, Ubuntu 24.04. Ein Server, der 500 gleichzeitige Prozesse mühelos schaffen sollte.

Bei 200 gleichzeitigen Trigger.dev-Jobs: Load Average 1018. SSH nicht erreichbar. Server praktisch tot.

CPU-Auslastung im Moment des Crashs: 16 Prozent. RAM-Auslastung: 12 Prozent.

Der Server hatte massig Kapazität übrig und war trotzdem komplett blockiert.

Was passiert war: 29.000 Jobs in einem Batch gestartet. Die Konfiguration erlaubte 300 gleichzeitige Ausführungen mit einem Burst-Faktor von 2,0x, also effektiv bis zu 600 Pods. 354 davon hingen gleichzeitig in ContainerCreating fest. Load Average: 1018. SSH: tot. Dashboard: tot. Der Server war nicht erreichbar.

Nicht wegen zu wenig Ressourcen. Wegen eines einzigen Kernel-Mechanismus.

Der net_mutex-Engpass

Trigger.dev v4 im Kubernetes-Modus erzeugt für jeden einzelnen Task-Run einen neuen Kubernetes-Pod. Einen Pod zu erzeugen bedeutet, einen Linux-Network-Namespace zu erzeugen. Und im Kernel ist diese Operation durch ein globales Lock geschützt: net_mutex.

Eine Namespace-Operation nach der anderen. Auf einem einzelnen Node ändert die Anzahl der CPU-Kerne daran nichts. Ein 96-Kern-Server und ein 4-Kern-Server laufen hier gegen dieselbe Wand.

Wenn 300 Pods gleichzeitig hochfahren wollen, warten 299 Prozesse auf dieses eine Lock. Sie gehen in den Kernel-Zustand uninterruptible sleep, kurz D-State. Der Load Average explodiert. Der Server reagiert nicht mehr.

containerd, die Runtime, die Kubernetes nutzt, erzeugt in so einem Setup physisch maximal etwa 1,7 neue Pods pro Sekunde auf einem einzelnen Node. Um präzise zu sein: Das ist, was ich in meiner Konfiguration gemessen habe, mit dieser Kernel-Version und diesem CNI. Es ist keine Zahl, die du mit einem Config-Flag wegtunest, und mehr Kerne verschieben sie nicht.

Wie Trigger.dev Cloud dasselbe Problem löst

Trigger.dev Cloud betreibt dieselbe Architektur, aber sie betreiben tausende Nodes pro Region. 300 Pods, verteilt auf 300 verschiedene Nodes, bedeuten einen Pod pro Node und keine Lock-Contention. Das ist keine bessere Software. Es ist einfach mehr Hardware, die du nicht siehst und nicht bezahlst — bis die Rechnung kommt.

Self-Hosting auf einem einzelnen Node ist strukturell etwas anderes als ihre Cloud. Das steht nirgends in der Dokumentation. Es ist das mit Abstand Wichtigste, das man vor dem Start verstehen muss — und du entdeckst es erst, wenn du schon committet bist.

Was ich gebaut habe, um es zu umgehen

Ich habe das System nicht aufgegeben. Ich habe drei Monate damit verbracht, es zu zähmen. Alles Folgende teile ich vollständig, denn der Wert liegt nicht im taktischen Wissen. Der Wert ist die Schlussfolgerung am Ende.

Cilium statt Flannel

Flannel als CNI-Plugin verursacht heftige D-State-Stürme bei massenhafter Pod-Erzeugung. Kernel-Network-Namespace-Operationen blockieren auf eine Art, die Flannel verschlimmert. Ich habe Flannel durch Cilium mit eBPF ersetzt, was D-State bei 200 gleichzeitigen Pods von 2500+ auf unter 10 reduziert hat.

Es löst das net_mutex-Problem nicht. Es macht es beherrschbar.

Der eigene Warm-Start-Broker

Das eigentliche Problem ist, dass jeder Run einen neuen Pod erzeugt, und der Pod-Boot dauert 5 bis 15 Sekunden. Bei tausenden Runs ist das der Engpass.

Meine Lösung war ein eigener Node.js-Dienst, der als Broker zwischen Supervisor und Runner-Pods agiert. Nachdem ein Run fertig ist, wartet der Runner-Pod, statt sich zu beenden. Er pollt den Warm-Start-Dienst per Long-Poll. Wenn ein neuer Run für dasselbe Deployment und dieselbe Maschinengröße reinkommt, übergibt der Dienst den Run direkt an den wartenden Pod — kein neuer Pod nötig.

Der Dienst nutzt einen zweistufigen Index für O(1)-Lookup: einen Deployment-Index, der Deployment-IDs auf wartende Worker abbildet, und einen Worker-Index, der Controller-IDs auf Deployment-Buckets abbildet. Maximal 750 Worker warten gleichzeitig.

Ergebnis in Produktion: eine Trefferquote von 91,4 Prozent in frühen Stresstests. 2103 Treffer, 198 Fehlschläge. 131 Worker gleichzeitig im Pool wartend. Warm-Start-Latenz: 1 ms innerhalb des Dienstes, 930 ms Ende zu Ende. Ohne Warm-Start: 5 bis 15 Sekunden pro Run.

Das ist ein komplett eigener Dienst, von Grund auf gebaut, einzig dafür, Trigger.dev im Volumen zum Laufen zu bringen.

Der Zombie-Killer

Manche Tasks blieben dauerhaft im Status EXECUTING hängen — nicht, weil sie liefen, sondern weil ein Deadlock sie nie fertig werden ließ. Der Supervisor entnahm für diese Slots keine neuen Runs aus der Queue. Die Concurrency wurde von Zombies als Geisel gehalten.

Fix: ein Python-Skript, das als systemd-Dienst läuft, alle 60 Sekunden die PostgreSQL-Datenbank prüft und Tasks, die länger als 15 Minuten in EXECUTING hängen, auf CANCELED setzt.

Das läuft als zombie-killer.service im Hintergrund. Dauerhaft. In Produktion.

Die Recovery-Prozedur nach einem D-State-Crash

Wenn der Server unten war, war die Wiederherstellung nicht trivial. SSH war nicht erreichbar, also bin ich über die Hetzner-Konsole rein. Dann: k3s stoppen, die k3s-SQLite-Datenbank direkt manipulieren, k3s neu starten.

Ergebnis: null verlorene Runs. Aber das funktionierte nur, weil ich das Playbook vorher geschrieben hatte. Triffst du das ohne eines, stellst du aus dem Backup wieder her.

Die operativen Fallen, die niemand dokumentiert

Das sind die Probleme, die nicht in einem D-State-Sturm enden. Sie bauen sich langsam auf und verursachen irgendwann einen Ausfall in Produktion. Keine davon steht in der offiziellen Dokumentation.

Falle 1: kubectl set env verwaist Runs dauerhaft

Du willst eine Umgebungsvariable aktualisieren. Du führst kubectl set env deployment/trigger-webapp ... aus. Das löst automatisch einen Deployment-Rollout aus, also startet die Webapp neu.

Was passiert: Redis-Queue-Einträge (engine:runqueue:*) gehen verloren. Runs, die in PostgreSQL als PENDING oder EXECUTING liegen, haben keine Redis-Einträge mehr. Sie werden nie aus der Queue genommen. Für immer verwaist. Du musst sie einzeln canceln und neu auslösen.

Fix: Umgebungsvariablen nur über Helm-Values setzen, niemals über kubectl set env, während Runs aktiv sind. Das ist nirgends dokumentiert.

Falle 2: Caddyfile nach jedem Helm-Deploy manuell aktualisieren

Caddy läuft als Reverse Proxy und routet Traffic zu Webapp und Registry über deren Kubernetes-ClusterIPs. Diese ClusterIPs ändern sich nach jedem helm install oder helm upgrade.

Nach jedem Deploy: kubectl get services -n trigger, die neuen IPs notieren, das Caddyfile aktualisieren, systemctl reload caddy. Vergiss das, und das Dashboard ist nicht erreichbar und CI/CD-Deploys schlagen fehl.

Falle 3: Docker-Registry-401 bei CI/CD, ein stiller Fehler

Die generateRegistryCredentials-API der Trigger.dev-CLI unterstützt nur AWS ECR. Für selbst gehostete Registries gibt sie einen 409-Fehler still zurück, ohne Error-Log in der Ausgabe. Der buildx-docker-container-Treiber erbt die Host-Docker-Credentials nicht. Ergebnis: jeder CI/CD-Deploy scheitert mit 401 Unauthorized.

Das zu debuggen kostet Zeit. Der Fix ist ein expliziter docker login-Schritt in der GitHub-Actions-Pipeline — aber zuerst musst du überhaupt verstehen, worin das Problem besteht.

Falle 4: Electric SQL muss PgBouncer umgehen

Electric SQL braucht wal_level = logical und direkte logische Replikation. PgBouncer im Transaction-Modus unterstützt das nicht.

Der Helm-Chart ignoriert die Electric-extraEnvVars-Konfiguration und nutzt trotzdem die PgBouncer-URL (Port 6432). Electric muss direkt mit Port 5432 verbinden. Du musst die Env-Var auf dem Electric-Deployment nach jedem Deploy manuell setzen — und erneut nach jedem helm upgrade, weil das Upgrade sie überschreibt.

Falle 5: Die PostgreSQL-Ressourcen-Falle

Das Standard-Setup des Helm-Charts weist PostgreSQL 50 Kerne und 129 GB RAM zu. Das klingt nach einer soliden Datenbank-Konfiguration.

Trigger.dev nutzt PostgreSQL nur für kleine, indexierte OLTP-Queries über das Prisma-ORM. Die Run-Queue liegt in Redis. Logs liegen in ClickHouse. PostgreSQL ist nur der State of Record.

Tatsächlicher Bedarf: 8 bis 12 Kerne, 32 bis 48 GB RAM.

Der Effekt: 40 Kerne und 80 GB RAM sind für ein PostgreSQL reserviert, das sie nie braucht — und stehen den Runner-Pods nicht zur Verfügung, die sie tatsächlich brauchen.

Der Teil, der wirklich zählt: Das ist eine Tooling-Entscheidung — und Claude Code hat sie verändert

Hier ist das, was ich vor Monat eins hätte verstehen sollen. Der schwere Teil bei Hintergrund-Jobs ist nicht der Code. Es ist die Wahl einer Architektur, die zu deiner tatsächlichen Last passt — damit du nie um 2 Uhr nachts einen Zombie-Killer schreibst.

Und diese Entscheidung hat sich kürzlich verändert, wegen der Art, wie diese Systeme heute gebaut werden. Mit Claude Code kollabiert der Implementierungsaufwand für jedes der Tools unten. Du beschreibst, was ein Job tun soll, Claude schreibt ihn, und beim richtigen Tool deployt es ihn und verdrahtet den Webhook für dich. Der Engpass ist nicht mehr „kann ich das bauen“. Er ist „habe ich das Richtige zum Draufbauen gewählt“. Das ist jetzt das ganze Spiel.

Deshalb zählt die Integrationstiefe mehr als früher. Windmill hat ein offizielles windmill-claude-plugin und einen MCP-Server. Claude Code kennt Windmill-spezifische Muster, CLI-Konventionen und Skript-Strukturen und interagiert direkt mit dem Workspace: Jobs auslösen, Status abfragen, Skripte deployen — alles per Chat. Die Feedback-Schleife ist kürzer als bei jedem anderen Tool hier. pg-boss und Celery funktionieren ebenfalls sehr gut mit Claude Code, ohne Plugin, aber mit tiefer Trainingsabdeckung. Trigger.dev hat keine offizielle Integration.

Wenn du ein Dienstleistungsgeschäft oder eine interne Automatisierungsebene baust und kein Vollzeit-Infrastruktur-Engineer bist, ist das der Hebel. Das Tool, das du mit Claude Code bedienen und erweitern kannst, auf einer Architektur, die nicht gegen dich arbeitet, schlägt das technisch beeindruckende Tool, das einen Spezialisten braucht.

Tool-Vergleich: Was tatsächlich funktioniert

Trigger.dev Cloud

Was es ist: Managed-Plattform für Hintergrund-Jobs. Pod-pro-Task-Isolation, Durable Execution, Echtzeit-Monitoring.

Wann es Sinn ergibt: Du brauchst echte Durable Execution. Tasks laufen über Stunden oder Tage. Du willst keine Infrastruktur verwalten.

Kosten: Bei 100.000 Ausführungen pro Tag mit 5 Sekunden Laufzeit: 150.000 $+ pro Monat allein an Invocation-Gebühren, vor Compute. Selbst bei einem Bruchteil dieses Volumens wird Trigger.dev Cloud schnell teuer.

Self-Hosted: Technisch möglich, operativ schwer. Das net_mutex-Problem, die fünf Fallen oben, der Warm-Start-Broker, der Zombie-Killer. Plane 4 bis 8 Wochen Infrastruktur-Zeit ein, bevor das System zuverlässig läuft.

Claude Code: Keine offizielle Integration. TypeScript-SDK verfügbar.

Windmill

Was es ist: Open-Source-Workflow-Engine. Python, TypeScript, Go, Bash, SQL. Jedes Skript bekommt automatisch einen Webhook-Endpunkt. Dashboard und Logs eingebaut. Eine direkte n8n-Alternative für Teams, die Code dem Drag-and-drop vorziehen.

Architektur: Worker sind langlebige Prozesse, die Jobs aus einer PostgreSQL-Queue ziehen. Kein Pod pro Task. Kein net_mutex. Kein Kubernetes nötig.

Dedizierte Worker: Für hochfrequente Skripte läuft ein für ein einzelnes Skript reservierter Worker als While-Loop mit 0 ms Cold Start.

Claude Code: Offizielles windmill-claude-plugin. MCP-Server eingebaut. Die stärkste Claude-Code-Integration aller Tools hier.

Kosten self-hosted: Community Edition: kostenlos. Unbegrenzte Ausführungen, AGPLv3. Enterprise-Features (SAML, Audit-Logs, Git-Sync, S3-Cache) kosten extra und werden für die meisten Self-Hosted-Setups nicht gebraucht.

Bekannter Nachteil: 60 ms Cold Start pro Job auf Standard-Workern. Auf dedizierten Workern: 0 ms. Für viele verschiedene, selten laufende Skripte sind Standard-Worker in Ordnung. Für hochfrequente Skripte nutze dedizierte Worker.

Celery + Redis (Python)

Was es ist: Der De-facto-Standard für Python-Hintergrund-Jobs seit 2011. Eine direkte n8n-Alternative für Python-Teams.

Architektur: Langlebige Worker-Prozesse, Redis-Queue, kein Container pro Job.

Bekannte Produktionsprobleme: Memory Leaks im Worker-Prozess. Fix: --max-tasks-per-child=100. Ohne explizites --concurrency nutzt Celery die CPU-Kern-Anzahl als Default, unkontrolliert auf 96 Kernen. Worker stoppen nach einem Redis-Reconnect, was einen expliziten Error-Handler mit Auto-Reconnect erfordert. Kein eingebautes Dashboard. Flower (Open Source) funktioniert, ist aber begrenzt.

Claude Code: Sehr gut. Keine offizielle Integration, aber etablierte Muster und tiefe Trainingsabdeckung.

BullMQ (JavaScript/TypeScript)

Was es ist: Redis-basierte Job-Queue für Node.js. MIT-Lizenz. Eine n8n-Alternative für TypeScript-Teams.

Bekannte Produktionsprobleme: FlowProducer.add() kann still ohne Fehler scheitern und den Job ins Nirgendwo schicken. Geplante Jobs stoppen nach ein paar Stunden zufällig. Redis maxmemory-policy muss noeviction sein; allkeys-lru löscht BullMQ-Keys.

Claude Code: Gut. TypeScript-first macht die Zusammenarbeit reibungslos.

pg-boss (PostgreSQL-nativ)

Was es ist: Eine Job-Queue-Bibliothek, die PostgreSQL als einziges Backend nutzt. MIT-Lizenz. Seit 2016 dabei, 268.000 wöchentliche npm-Downloads. Eine n8n-Alternative für Teams, die ohnehin schon PostgreSQL betreiben.

Kernvorteil: Transaktionales Enqueue. Job- und Geschäftsdaten in einer einzigen ACID-Transaktion. Mit Redis-basierten Lösungen unmöglich.

Bekannte Produktionsprobleme: Kein Auto-Reconnect nach einem PostgreSQL-Restart; das implementierst du selbst. Die Performance verschlechtert sich jenseits von 500.000 bis 1 Million Zeilen ohne Autovacuum-Tuning. Standard-Einstellungen löschen Jobs nach 14 Tagen; unverarbeitete Jobs verschwinden. Bus-Faktor 1: ein einziger Haupt-Maintainer.

Wann es Sinn ergibt: PostgreSQL läuft schon. Null neue Infrastruktur. ACID-Garantien fürs Enqueue nötig.

Claude Code: Gut. Kleine Codebasis, klare TypeScript-API.

Vergleichstabelle

KriteriumTrigger.dev CloudWindmillCelerypg-boss
Kubernetes nötigNeinNeinNeinNein
Pod pro TaskJaNeinNeinNein
net_mutex-RisikoNeinNeinNeinNein
Cold StartNein0–60 ms0 ms0 ms
WebhooksAutomatischAutomatischSelbst bauenSelbst bauen
DashboardExzellentGutFlowerNur SQL
SprachenTypeScriptPython, TS, Go, SQLPythonJavaScript
Claude-Code-PluginNeinJa, offiziellNeinNein
Durable ExecutionJaNeinNeinNein
Kosten bei SkalierungSehr hochServerServerServer

Serverkosten und Ausführungslimits

Alle Zahlen unten sind Schätzungen, um dir ein Gefühl für die Größenordnung zu geben, keine Angebote. Dein tatsächlicher Bedarf hängt von Job-Dauer und Workload-Typ ab.

I/O-bound vs. rechenintensiv

I/O-bound: Das Skript wartet auf externe Dienste wie HTTP-Requests, API-Calls, Scraping, Datenbank-Queries. Der Worker-Prozess ist größtenteils idle. Ein Server kann weit mehr gleichzeitige I/O-bound-Jobs bewältigen, als er Kerne hat.

Rechenintensiv: Das Skript rechnet aktiv, etwa LLM-Inferenz auf eigener Hardware, Bildverarbeitung, Datentransformation. Faustregel: ein gleichzeitiger Job pro 2 CPU-Kerne.

Grobe Orientierung, self-hosted, I/O-bound (Windmill oder Celery)

Ausführungen/TagGleichzeitigServerKosten/Monat
bis 10.000bis 20VPS 4 Kerne / 8 GB5–15 €
bis 50.000bis 100VPS 8 Kerne / 16 GB20–35 €
bis 200.000bis 500VPS 16 Kerne / 32 GB50–80 €
bis 1.000.000bis 2.000Dediziert 32+ Kerne / 64+ GB90–160 €
über 1.000.000über 2.000Dediziert 96 Kerne / 256 GB200–250 €

Trigger.dev-Cloud-Kostenvergleich (geschätzt)

Ausführungen/TagLaufzeit/JobCloud-Kosten/MonatSelf-hosted/Monat
1.0005 sHunderte5–15 €
10.0005 sniedrige Tausende20–35 €
100.0005 shohe Tausende+50–80 €
1.000.0005 sZehntausende+200–250 €

Der Break-even liegt irgendwo bei 1.000 bis 2.000 Ausführungen pro Tag, je nach Job-Dauer und Maschinentyp. Darunter ist Cloud günstiger. Darüber wächst die Lücke schnell.

Die eine Frage, die zählt

Nach drei Monaten war das Wertvollste, das ich produziert habe, nicht der Warm-Start-Broker oder der Zombie-Killer. Es war die Antwort auf eine einzige Frage: Braucht diese Last tatsächlich Pod-pro-Task-Isolation und Durable Execution — oder braucht sie nur eine zuverlässige Queue mit Retries?

Für Scraping, Enrichment, KI-Scoring, Webhook-Verarbeitung und nahezu jede Automatisierung, die ein Dienstleistungsgeschäft betreibt, ist die Antwort die zweite. Und in dem Moment, in dem die Antwort die zweite ist, wird die gesamte Architektur von Trigger.dev — das Kubernetes, die Pods, die net_mutex-Wand — zu Kosten, die du für eine Garantie zahlst, die du nicht brauchst.

Diese eine Unterscheidung hätte mir drei Monate gespart. Sie ist mehr wert als jeder Workaround in diesem Artikel zusammen.

Meine Empfehlung

Für die meisten Anwendungsfälle: Windmill self-hosted. Webhooks automatisch, Dashboard eingebaut, Python und TypeScript nativ, offizielles Claude-Code-Plugin, kein Kubernetes. Community Edition kostenlos, ohne Ausführungslimits. Starte mit einem VPS für 20–35 €.

Alle Skripte in Python, maximale Kontrolle: Celery + Redis.

PostgreSQL läuft schon, ACID-Garantien nötig: pg-boss.

Du brauchst wirklich Durable Execution und willst keine Infrastruktur verwalten: Trigger.dev Cloud — nachdem du die Rechnung gemacht hast. Oberhalb von 2.000 Ausführungen pro Tag zahlt sich Self-Hosting mit dem richtigen Tool aus.

Wenn dein Problem weniger „welcher Job-Runner“ ist und mehr „meine Workflows hängen an einem Freelancer, den sonst niemand lesen kann“, ist das ein anderes und teureres Problem. Darüber habe ich separat geschrieben.

30-MINUTEN-GESPRÄCH

Welcher Job-Runner ist der richtige für deine Last?

Wir gehen deine Jobs durch, schätzen das Volumen und empfehlen das richtige Tool, bevor du Wochen in die falsche Grundlage steckst. Kein Pitch.

30 Minuten · Unverbindlich · Kostenlos