Kestra mit Docker Compose installieren und betreiben: Workflow-Orchestrierung im Eigenbetrieb
Kestra 2.0.6 mit Docker Compose und PostgreSQL installieren: Plattenplatz, Startreihenfolge, Absicherung von Docker-Socket und Basic-Auth, erster Flow, Backup, Update und typische Fehler.
Geprüft am 16.10.2026 · für Kestra 2.0.6
WerbelinksMit * markierte Links sind Werbelinks: Bei einem Kauf erhalten wir eine Provision, der Preis bleibt gleich. Als Amazon-Partner verdiene ich an qualifizierten Verkäufen. Mehr dazu

Wer Skripte, Datenbank-Exporte, API-Abfragen und Backup-Läufe in fester Reihenfolge starten muss, stößt mit einer wachsenden Cronjob-Sammlung an Grenzen: Es fehlt der Überblick über gescheiterte Läufe, ein sauberer Wiederanlauf und echte Abhängigkeiten zwischen Jobs. Kestra ist eine quelloffene Plattform (Apache-2.0) für diese Aufgabe. Workflows, bei Kestra „Flows“, werden als YAML beschrieben, zentral ausgeführt, protokolliert und in einer Weboberfläche sichtbar gemacht. Diese Anleitung installiert Kestra 2.0.6 mit Docker Compose und PostgreSQL und behandelt Sicherheit, Backup, Update und Deinstallation.
Voraussetzungen
Bei Kestra ist nicht der Arbeitsspeicher das Nadelöhr, sondern die Festplatte. Im Test belegte das Kestra-Image 2.0.6 entpackt 7,99 GB, dazu rund 650 MB für postgres:18. Ein Update lädt das neue Image, bevor das alte entfernt wird, der Bedarf ist dann kurzzeitig doppelt so hoch.
- Linux-Server mit Docker Engine und Compose v2, im Test Ubuntu mit Docker 29.1.3 und Compose 2.40.3.
- 2 CPU-Kerne und 4 GB RAM. Im Test belegte Kestra nach dem ersten Flow 1,14 GiB, PostgreSQL 83 MiB. Die Heap-Grenze richtet sich nach dem Host: Der Java-Prozess meldete in einem früheren Test auf einem kleineren Host
2702MB max memory, auf dem 2.0.6-Testhost mit 11 GiB RAM7992MB. - Mindestens 10 GB, besser 15 GB freier Speicher unter
/var/lib/docker. - Für den Zugriff aus dem Netz ein Reverse Proxy mit TLS.
Für wenige einfache Cronjobs ohne Abhängigkeiten oder auf einem 2-GB-VPS lohnt sich der zusätzliche Dienst nicht.
| Eckdatum | Wert |
|---|---|
| Image | kestra/kestra:v2.0.6, Datenbank postgres:18 |
| Port | 8080 (Weboberfläche und API), 8081 Management bleibt geschlossen |
| Volumes | postgres-data, kestra-data, Bind-Mount /tmp/kestra-wd |
| Konfiguration | YAML-Block in der Variablen KESTRA_CONFIGURATION, keine .env |
Schritt 1: Plattenplatz prüfen
Ein zu voller Datenträger ist der häufigste Abbruchgrund. Der Pull scheitert dann mit no space left on device, oft erst nach Minuten.
# Freien Platz vor der Installation pruefen
df -h /var/lib/docker
# Alte, nicht mehr verwendete Images entfernen und Platz zurueckgewinnen
docker image prune -a -f
docker image prune -a -f entfernt alle Images, die kein Container verwendet. Laufende Container auf demselben Host sind davon nicht betroffen.
Verifizieren: df -h /var/lib/docker zeigt mindestens 10 GB frei.
Schritt 2: Compose-Datei laden und anpassen
Die offizielle Compose-Datei liegt im Hauptrepository. So sah sie im Test aus:
volumes:
postgres-data:
driver: local
kestra-data:
driver: local
services:
postgres:
image: postgres:18
volumes:
- postgres-data:/var/lib/postgresql
environment:
POSTGRES_DB: kestra
POSTGRES_USER: kestra
POSTGRES_PASSWORD: k3str4
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
interval: 30s
timeout: 10s
retries: 10
kestra:
image: kestra/kestra:latest
pull_policy: always
stop_grace_period: 6m
user: "root"
command: server standalone
volumes:
- kestra-data:/app/storage
- /var/run/docker.sock:/var/run/docker.sock
- /tmp/kestra-wd:/tmp/kestra-wd
environment:
KESTRA_CONFIGURATION: |
datasources:
postgres:
url: jdbc:postgresql://postgres:5432/kestra
driverClassName: org.postgresql.Driver
username: kestra
password: k3str4
kestra:
repository:
type: postgres
storage:
type: local
local:
base-path: "/app/storage"
queue:
type: postgres
tasks:
tmp-dir:
path: /tmp/kestra-wd/tmp
url: http://localhost:8080/
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_started
Kestra liest seine Einstellungen nicht aus einer .env, sondern aus dem YAML-Block in KESTRA_CONFIGURATION. Die wichtigsten Werte:
| Wert | Bedeutung und Hinweis |
|---|---|
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD | Zugangsdaten der Datenbank, identisch unter datasources.postgres wiederholt. k3str4 ist ein Platzhalter. |
kestra.storage.local.base-path | Ablage für Task-Ausgaben, Volume kestra-data. |
kestra.url | Basis-URL für erzeugte Links, mit erreichbarem Hostnamen und Port. |
kestra.server.basic-auth | In der Vorlage auskommentiert, siehe Schritt 3. |
/var/run/docker.sock | Nötig für den Docker-Task-Runner, entspricht Root-Rechten auf dem Host (Schritt 3). |
stop_grace_period: 6m | Laufende Aufgaben bekommen beim Stoppen fünf Minuten, Compose bricht nicht vorher ab. |
Laden Sie die Datei und prüfen Sie die Syntax:
# Arbeitsverzeichnis fuer den Stack anlegen
mkdir -p /opt/kestra && cd /opt/kestra
# Offizielle Compose-Datei aus dem Projektrepository laden
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/kestra-io/kestra/v2.0.6/docker-compose.yml
# Syntax und aufgeloeste Variablen pruefen, bevor etwas startet
docker compose config -q
# Stack im Hintergrund starten, der Pull laeuft dabei mit
docker compose up -d
Vor dem Start nehmen Sie vier Änderungen vor. Ersetzen Sie k3str4 an beiden Stellen durch ein eigenes Passwort, denn dieser Wert steht in jeder öffentlichen Kopie der Datei. Setzen Sie statt latest eine feste Version und pull_policy: missing, sonst zieht jedes docker compose up ungefragt eine neue Version, womöglich beim Neustart nach einem Stromausfall:
image: kestra/kestra:v2.0.6
pull_policy: missing
restart: unless-stopped
Die Vorlage startet Kestra schon, wenn der PostgreSQL-Container läuft (service_started). Im Test brach Kestra 2.0.6 deshalb beim ersten Start mit Connection to postgres:5432 refused ab und blieb mit Exit-Code 1 stehen, da eine Neustart-Regel fehlt. Lassen Sie Kestra auf den vorhandenen Healthcheck warten:
depends_on:
postgres:
condition: service_healthy
Ändern Sie den Port, passen Sie kestra.url mit an. Im Test war Port 8080 belegt:
image: kestra/kestra:v2.0.6
ports:
- "18080:8080"
url: http://localhost:18080/
docker compose config -q prüft nur die YAML-Struktur, nicht, ob Port und URL zusammenpassen.
Verifizieren: grep -n "k3str4\|latest" docker-compose.yml liefert keine Treffer mehr, grep -n service_healthy docker-compose.yml zeigt eine Zeile, und docker compose config -q endet ohne Ausgabe.
Schritt 3: Zugriff absichern
Die offizielle Datei ist laut ihren Kommentaren für Entwicklung und Evaluierung gedacht. Drei Punkte verlangen eine Entscheidung.
Docker-Socket: /var/run/docker.sock im Container erlaubt, beliebige Container mit eingehängtem Host-Dateisystem zu starten, also Root-Rechte auf dem Host. Der Socket wird nur für den Docker-Task-Runner gebraucht. Ohne ihn entfernen Sie die Zeile und nutzen den Process-Runner. Der Eintrag user: "root" existiert nur wegen des Sockets, das Image selbst läuft als Benutzer kestra. Brauchen Sie den Docker-Runner, behandeln Sie den Host wie einen privilegierten Dienst und begrenzen den Zugang zur Oberfläche eng.
Basic-Auth: Der Block ist in der Vorlage auskommentiert. Ohne Anmeldung kann jeder, der den Port erreicht, Flows anlegen und damit Code auf dem Host ausführen. Laut Kommentaren der Compose-Datei muss der Benutzername eine E-Mail-Adresse sein, das Passwort mindestens acht Zeichen mit Großbuchstabe und Ziffer haben:
kestra:
server:
basic-auth:
username: admin@example.com
password: GeheimesPasswort1
Ports: Binden Sie Kestra mit 127.0.0.1:8080:8080 an localhost und setzen Sie einen Reverse Proxy mit TLS davor. Der Management-Port 8081 bleibt auskommentiert, er ist unauthentifiziert. PostgreSQL veröffentlicht keinen Port. Schränken Sie den Zugriff auf das Verzeichnis ein, denn dort stehen Zugangsdaten im Klartext.
Verifizieren: Nach dem Start in Schritt 4 zeigt ss -tlnp | grep -E '8080|8081' nur 127.0.0.1:8080. Schon vorher listet docker compose config | grep -A3 basic-auth Ihren Benutzer.
Schritt 4: Stack starten und ersten Start beobachten
Starten Sie mit docker compose up -d und verfolgen Sie das Log:
# Logs des Kestra-Dienstes verfolgen
docker compose logs -f kestra
# Nur die Migrationsmeldungen herausfiltern
docker compose logs kestra | grep -i migration
Der Download dominiert die Wartezeit, der Start selbst ist kurz. So verlief er im Test mit 2.0.6 (Sekunden ab Start des Kestra-Containers):
| Zeitpunkt | Beobachtetes Ereignis |
|---|---|
| vorher | PostgreSQL meldet healthy, erst dann startet Kestra. |
| +3 s bis +7 s | Log: Applying migration [0-init] bis [2.1.02-trigger-source-disabled], jeweils applied successfully. |
| +7 s | Log: Updating instance version from none to 2.0.6 |
| +9 s | Log: Loaded 9 "Getting Started" flows from community blueprints. |
| +10 s | Executor, Controller auf Port 50051, Scheduler, Indexer und Worker melden sich als gestartet. |
| unter +10 s | Erster HTTP 200 auf /ui/. Auf einem Host mit 2 Kernen dauerte das in einem früheren Test rund 48 Sekunden, bis dahin liefert curl den Code 000. |
Beim Prüfen per Kommandozeile fällt das Verhalten der API auf:
# Wurzelpfad: leitet auf die Oberflaeche um
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18080/
# Oberflaeche selbst: liefert 200
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18080/ui/
# API ohne Anmeldung: liefert 401
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:18080/api/v1/configs
# API mit Basic-Auth: liefert 200 und die Version
curl -s -u admin@example.com:GeheimesPasswort1 http://127.0.0.1:18080/api/v1/configs
Im Test lieferte GET / den Status 307 mit Umleitung auf /ui/, GET /ui/ 200 und GET /api/v1/configs ohne Anmeldung 401 mit "title":"Authentication required", mit Basic-Auth 200 und "version":"2.0.6". Den Pfad /api/v1/version beantwortet 2.0.6 auch angemeldet mit 404. Ein 401 belegt also, dass der Dienst antwortet. Prüfen Sie Erreichbarkeit im Monitoring gegen /ui/, nicht gegen die API.
Verifizieren: docker compose ps zeigt beide Container als running (PostgreSQL mit healthy), das Log enthält die Meldungen zum Start von Executor, Scheduler und Worker, und /ui/ liefert HTTP 200.
Schritt 5: Ersten Flow ausführen
Legen Sie in der Oberfläche unter Flows diesen Flow an und starten Sie ihn von Hand:
id: erster_test
namespace: test.sedv
tasks:
- id: hallo
type: io.kestra.plugin.core.log.Log
message: "Kestra laeuft, Ausfuehrung {{ execution.id }}"
- id: datum
type: io.kestra.plugin.scripts.shell.Commands
taskRunner:
type: io.kestra.plugin.core.runner.Process
commands:
- date -u +"%Y-%m-%dT%H:%M:%SZ"
Der erste Task schreibt eine Logzeile, der zweite führt ein Shell-Kommando aus. Der Process-Runner startet es direkt im Kestra-Container und braucht keinen Docker-Socket.



Verifizieren: Unter Executions erscheint der Lauf mit Status SUCCESS und den Logs beider Tasks, darunter das aktuelle Datum in UTC. Damit arbeiten Datenbank, Queue, Scheduler und Worker.
Schritt 6: Backup einrichten
Gesichert werden zwei Dinge: die Datenbank mit Flows und Ausführungshistorie sowie der interne Storage mit Task-Ausgaben.
| Ablageort | Pfad im Container | Inhalt |
|---|---|---|
Volume postgres-data | /var/lib/postgresql | Flows, Ausführungshistorie, Queue und sämtliche Metadaten. Der wichtigste Datenbestand. |
Volume kestra-data | /app/storage | Interner Storage mit Task-Ausgaben und von Flows erzeugten Dateien. |
Bind-Mount /tmp/kestra-wd | /tmp/kestra-wd | Reines Arbeitsverzeichnis laufender Tasks. Nicht sicherungswürdig, verursacht aber beim Aufräumen Probleme. |
Beide Sicherungsbefehle liefen im Test, der Dump einer frischen Instanz mit den Beispiel-Flows war 187 KB groß. Die Option --clean --if-exists schreibt DROP-Befehle in den Dump: Ohne sie meldete das Zurückspielen in die bestehende Datenbank im Test 142 Fehler wegen bereits vorhandener Objekte, mit ihr keinen. Der Volumename trägt den Projektnamen als Präfix, bei /opt/kestra also kestra_kestra-data. Prüfen Sie ihn mit docker volume ls.
# Datenbank als SQL-Dump sichern
docker compose exec -T postgres pg_dump -U kestra -d kestra --clean --if-exists > kestra.sql
# Internen Storage als Tar-Archiv sichern
docker run --rm -v kestra_kestra-data:/d -v "$PWD":/b alpine tar czf /b/kestra-storage.tgz -C /d .
Zur Wiederherstellung halten Sie Kestra an, lassen PostgreSQL laufen und spielen Dump und Archiv zurück:
# Kestra anhalten, Postgres weiterlaufen lassen
docker compose stop kestra
# Datenbank aus dem Dump zurueckspielen
cat kestra.sql | docker compose exec -T postgres psql -U kestra -d kestra
# Internen Storage aus dem Archiv zurueckspielen
docker run --rm -v kestra_kestra-data:/d -v "$PWD":/b alpine sh -c 'rm -rf /d/* && tar xzf /b/kestra-storage.tgz -C /d'
# Kestra wieder starten
docker compose up -d kestra
Üben Sie die Wiederherstellung einmal auf einem zweiten Host und übertragen Sie Sicherungen auf ein anderes System.
Verifizieren: Nach dem Zurückspielen meldet psql keine ERROR-Zeilen und /ui/ liefert wieder HTTP 200. kestra.sql beginnt mit dem Kopf PostgreSQL database dump, tar tzf kestra-storage.tgz listet den Storage-Inhalt.
Schritt 7: Kestra aktualisieren
Lesen Sie die Release-Hinweise, sichern Sie die Datenbank, ändern Sie den Tag in der Compose-Datei und starten Sie neu:
# Vor jedem Update: Datenbank sichern
docker compose exec -T postgres pg_dump -U kestra -d kestra --clean --if-exists > kestra-vor-update.sql
# Neues Image laden und Container ersetzen
docker compose pull
docker compose up -d
# Migrationsmeldungen kontrollieren
docker compose logs -f kestra | grep -i migration
Kestra wendet Migrationen beim Start selbst an und quittiert jede mit applied successfully, ein manueller Schritt ist nicht nötig. Laut Upgrade-Dokumentation dürfen Sie bei Sprüngen über eine Hauptversion (etwa 1.3 auf 2.0) keine Zwischenversion überspringen, und ein Downgrade nach angewandter Migration wird nicht unterstützt. Der einzige Rückweg ist der Dump von vor dem Update.
Verifizieren: Das Log zeigt die Zeile Updating instance version mit der neuen Version und keine Migrationsfehler, /ui/ liefert HTTP 200.
Typische Fehler
Pull bricht mit no space left on device ab. Platz schaffen wie in Schritt 1 und neu starten. Bereits geladene Layer werden wiederverwendet.
Warnung zu Prometheus-Metriken im Log. Im Test erschien:
WARN i.m.p.PrometheusMeterRegistry The meter (MeterId{name='netty.alloc.arena.allocation.count', ...})
registration has failed: Prometheus requires that all meters with the same name have the same set of tag keys.
Der Dienst arbeitet normal, die Meldung können Sie ignorieren.
Kestra-Container beendet mit Exit 1, Log zeigt Connection to postgres:5432 refused. Kestra startete vor der Datenbank. Setzen Sie condition: service_healthy und restart: unless-stopped wie in Schritt 2 und starten Sie mit docker compose up -d neu.
Oberfläche nicht erreichbar, Container läuft. In den ersten rund 45 Sekunden liefert curl den Code 000, weil der Java-Prozess noch startet. Bleibt es dabei, prüfen Sie Portkollisionen und Logs:
# Laufende Container und Portzuordnung anzeigen
docker compose ps
# Pruefen, ob der Hostport bereits belegt ist
ss -tulpn | grep 8080
# Letzte Logzeilen des Dienstes ansehen
docker compose logs --tail 50 kestra
Monitoring gegen die API meldet dauerhaft Fehler. Die API antwortet ohne Anmeldung mit 401, prüfen Sie gegen /ui/.
Links zeigen auf den falschen Port. kestra.url passt nicht zur Portzuordnung.
Stoppen dauert lange. stop_grace_period: 6m gibt laufenden Tasks Zeit. Im Leerlauf verkürzt docker compose stop -t 20 die Wartezeit.
/tmp/kestra-wd lässt sich nicht löschen. Siehe Häufige Fragen.
Häufige Fragen
Wann ist n8n oder Airflow die bessere Wahl?
Für SaaS-Verknüpfungen ist n8n mit fertigen Integrationen meist schneller, für Python-Datenpipelines Airflow ausgereifter, aber schwerer. Kestra ist sprachunabhängig, versionierbar und kommt mit zwei Containern aus.
Wie deinstalliere ich Kestra vollständig?
Achtung: docker compose down -v löscht Datenbank und Storage unwiderruflich. Sichern Sie vorher wie in Schritt 6.
# Container, Netzwerk und benannte Volumes entfernen
cd /opt/kestra && docker compose down -v
# Stack-Verzeichnis loeschen
cd .. && rm -rf /opt/kestra
Im Test blieb danach das Arbeitsverzeichnis stehen:
rm: cannot remove '/tmp/kestra-wd': Operation not permitted
Kestra legt /tmp/kestra-wd als root an, down -v fasst Bind-Mounts nicht an. Entfernen Sie es mit sudo oder über einen kurzlebigen Container:
sudo rm -rf /tmp/kestra-wd
docker run --rm -v /tmp:/t alpine rm -rf /t/kestra-wd
# Kontrolle: beides muss leer bleiben
docker ps -a | grep kestra
docker volume ls | grep kestra
# Nicht mehr verwendete Images entfernen
docker image prune -a -f
Läuft Kestra auch ohne Docker-Socket?
Ja. Ohne Socket stehen Tasks mit dem Process-Runner zur Verfügung, wie im Test-Flow aus Schritt 5. Nur Tasks in eigenen Containern brauchen den Socket.
Fazit
Kestra lief im Test nach der Korrektur der Startreihenfolge zuverlässig, war Sekunden nach dem Containerstart bedienbar und kam nach dem ersten Flow mit gut 1,1 GiB RAM aus. Der Preis liegt beim knapp 8 GB großen Image und beim Docker-Socket in der Standardkonfiguration, der eine bewusste Entscheidung verlangt. Wer Plattenplatz einplant, die Version festlegt, Basic-Auth aktiviert und die Wiederherstellung einmal übt, löst damit eine gewachsene Cronjob-Sammlung ordentlich ab.
Weiterführende Anleitungen und Quellen
- Docker Compose Grundlagen und Stacks: Aufbau, Volumes und Netzwerke einer Compose-Datei.
- Docker Compose absichern: Secrets, Healthchecks, Non-Root: Passwörter aus der Compose-Datei herausziehen.
- PostgreSQL-Backup mit pg_dump und pg_restore: Datenbanksicherung im Detail.
- GitHub: kestra-io/kestra
- Offizielle docker-compose.yml des Projekts
- Kestra-Dokumentation: Upgrades
- Kestra-Dokumentation


