Temporal mit Docker installieren: Durable-Execution-Plattform für fehlertolerante Workflow-Orchestrierung
Temporal persistiert jeden Workflow-Schritt in PostgreSQL – kein Job geht verloren, auch nach Absturz oder Neustart. Diese Anleitung zeigt das aktuelle Docker-Compose-Setup mit temporalio/server 1.31.0 und PostgreSQL 16.

Kein Job darf verloren gehen – das ist die Kernaussage hinter Temporal, einer Open-Source-Plattform für Durable Execution. Der gesamte Workflow-Zustand wird nach jedem Schritt in einer Datenbank persistiert; bricht ein Worker-Prozess ab, läuft der Workflow genau dort weiter, wo er aufgehört hat – ganz ohne Boilerplate-Fehlerbehandlung im eigenen Code. Temporal eignet sich für verteilte Transaktionen nach dem Saga-Pattern, langlaufende Prozesse (Minuten bis Monate), Zahlungsabwicklung, Onboarding-Flows und KI-Agenten-Orchestrierung. Mit über 20.000 GitHub-Stars und mehr als 900.000 wöchentlichen Docker-Pulls ist es die meistgenutzte Open-Source-Workflow-Engine für Microservices. Diese Anleitung richtet sich an Entwickler und Administratoren, die Temporal auf einem beliebigen Linux-Host (Ubuntu/Debian, VM, NAS mit Docker) lokal oder auf einem kleinen Produktivserver betreiben möchten.
Voraussetzungen
- Docker Engine >= 24.x und das Docker Compose Plugin >= 2.x müssen installiert sein. Prüfe mit
docker compose version. Eine vollständige Installationsanleitung findest du in Docker und Docker Compose auf Linux installieren. - git für das Klonen des offiziellen
samples-server-Repositories (Skripte und dynamische Konfiguration werden zwingend benötigt). - Mindestens 4 GB freier RAM (Temporal-Server + PostgreSQL + UI; mit Elasticsearch: 8 GB empfohlen).
- Mindestens 10 GB freier Festplattenplatz für Images und das PostgreSQL-Daten-Volume (~120 MB Images für die PostgreSQL-only-Variante).
- Internet-Zugang für den initialen Image-Download.
- Optionaler Browser (Chrome/Firefox) für die Web-UI auf
localhost:8080. - Sicherheitshinweis: Temporal-Dienste dürfen niemals direkt im Internet erreichbar sein – behandle sie wie eine Datenbank und betreibe sie ausschließlich in vertrauenswürdigen internen Netzwerken oder hinter einem Reverse Proxy mit Authentifizierung. Für HTTPS-Termination eignet sich z. B. Traefik als Docker-Reverse-Proxy.
Eckdaten auf einen Blick
| Eigenschaft | Wert |
|---|---|
| Temporal-Server-Image | temporalio/server:1.31.0 |
| Admin-Tools-Image | temporalio/admin-tools:1.31.0 |
| Web-UI-Image | temporalio/ui:2.49.1 |
| Datenbank-Image | postgres:16 |
| gRPC-Port (Pflicht) | 7233 |
| Web-UI-Port | 8080 |
| PostgreSQL-Port | 5432 (intern; optional nach außen) |
| RAM-Bedarf | mind. 4 GB |
| Installationszeit | ca. 20 Minuten |
| Lizenz | MIT (Open Source) |
| Volume / Mount | Zweck |
|---|---|
| postgres_data | Alle Workflow-Historien und -Zustände in PostgreSQL (Named Volume, Pflicht für Persistenz) |
| ./dynamicconfig | Enthält development-sql.yaml für Rate-Limits und Feature-Flags (Pflicht, muss vor Start existieren) |
| ./scripts | Shell-Skripte für Schema-Setup und Namespace-Anlegen (aus samples-server-Repository) |
Schritt 1: Repository klonen und Projektordner vorbereiten
Das offizielle temporalio/docker-compose-Repository ist archiviert. Die aktuellen Compose-Dateien und die zwingend benötigten Shell-Skripte liegen im samples-server-Repository. Klone es und wechsle in das richtige Unterverzeichnis:
git clone https://github.com/temporalio/samples-server.git
cd samples-server/composeIm Verzeichnis compose/ findest du bereits fertige Compose-Varianten. Für die PostgreSQL-only-Installation (ohne Elasticsearch) arbeitest du wahlweise mit der vorhandenen docker-compose-postgres.yml als Referenz oder legst – wie im nächsten Schritt gezeigt – eine eigene compose.yaml im selben Verzeichnis an. Wichtig: Das Unterverzeichnis scripts/ und dynamicconfig/ müssen relativ zum Compose-Verzeichnis vorhanden sein. Prüfe das kurz:
ls scripts/ dynamicconfig/Beide Verzeichnisse sollten existieren und die Dateien setup-postgres.sh, create-namespace.sh sowie dynamicconfig/development-sql.yaml enthalten.
Verifizieren: Der Befehl ls scripts/setup-postgres.sh scripts/create-namespace.sh dynamicconfig/development-sql.yaml muss alle drei Dateien ohne Fehler ausgeben. Fehlt eine Datei, bitte nochmals prüfen, ob du im richtigen Verzeichnis (samples-server/compose) bist.
Schritt 2: .env-Datei mit gepinnten Versionen anlegen
Erstelle eine .env-Datei im Compose-Verzeichnis. Immer konkrete Image-Tags pinnen – niemals :latest verwenden, da Datenbankschema-Änderungen zwischen Versionen zu Inkompatibilitäten führen können. Wichtig: TEMPORAL_VERSION und TEMPORAL_ADMINTOOLS_VERSION müssen identisch sein.
# Temporal Docker Compose – Versionspinning
POSTGRESQL_VERSION=16
TEMPORAL_VERSION=1.31.0
TEMPORAL_ADMINTOOLS_VERSION=1.31.0
TEMPORAL_UI_VERSION=2.49.1
# Datenbank-Zugangsdaten (in Produktion starke Passwörter setzen!)
POSTGRES_USER=temporal
POSTGRES_PASSWORD=temporalVerifizieren: cat .env zeigt alle sechs Variablen. Achte darauf, dass keine Leerzeichen um das =-Zeichen stehen.
Schritt 3: compose.yaml anlegen
Lege die folgende compose.yaml im Compose-Verzeichnis an. Der Stack startet fünf Dienste in definierter Reihenfolge: PostgreSQL wird zuerst hochgefahren und per Healthcheck geprüft; erst dann führt temporal-admin-tools das Schema-Setup durch; nach dessen Abschluss startet temporal; sobald er gesund ist, wird der Namespace angelegt und die UI gestartet.
services:
postgresql:
image: postgres:${POSTGRESQL_VERSION:-16}
container_name: temporal-postgresql
restart: unless-stopped
ports:
- "5432:5432"
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-temporal}
POSTGRES_USER: ${POSTGRES_USER:-temporal}
networks:
- temporal-network
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U temporal"]
interval: 5s
timeout: 5s
retries: 60
start_period: 30s
temporal-admin-tools:
image: temporalio/admin-tools:${TEMPORAL_ADMINTOOLS_VERSION:-1.31.0}
container_name: temporal-admin-tools
restart: on-failure:6
depends_on:
postgresql:
condition: service_healthy
environment:
- DB=postgres12
- DB_PORT=5432
- POSTGRES_USER=${POSTGRES_USER:-temporal}
- POSTGRES_PWD=${POSTGRES_PASSWORD:-temporal}
- POSTGRES_SEEDS=postgresql
- SQL_PASSWORD=${POSTGRES_PASSWORD:-temporal}
networks:
- temporal-network
volumes:
- ./scripts:/scripts
entrypoint: ["/bin/sh"]
command: /scripts/setup-postgres.sh
temporal:
image: temporalio/server:${TEMPORAL_VERSION:-1.31.0}
container_name: temporal
restart: unless-stopped
depends_on:
temporal-admin-tools:
condition: service_completed_successfully
environment:
- DB=postgres12
- DB_PORT=5432
- POSTGRES_USER=${POSTGRES_USER:-temporal}
- POSTGRES_PWD=${POSTGRES_PASSWORD:-temporal}
- POSTGRES_SEEDS=postgresql
- BIND_ON_IP=0.0.0.0
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
networks:
- temporal-network
ports:
- "7233:7233"
volumes:
- ./dynamicconfig:/etc/temporal/config/dynamicconfig
healthcheck:
test: ["CMD", "nc", "-z", "localhost", "7233"]
interval: 5s
timeout: 3s
start_period: 30s
retries: 60
temporal-create-namespace:
image: temporalio/admin-tools:${TEMPORAL_ADMINTOOLS_VERSION:-1.31.0}
container_name: temporal-create-namespace
restart: on-failure:5
depends_on:
temporal:
condition: service_healthy
environment:
- TEMPORAL_ADDRESS=temporal:7233
- DEFAULT_NAMESPACE=default
networks:
- temporal-network
volumes:
- ./scripts:/scripts
entrypoint: ["/bin/sh"]
command: /scripts/create-namespace.sh
temporal-ui:
image: temporalio/ui:${TEMPORAL_UI_VERSION:-2.49.1}
container_name: temporal-ui
restart: unless-stopped
depends_on:
temporal:
condition: service_healthy
environment:
- TEMPORAL_ADDRESS=temporal:7233
- TEMPORAL_CORS_ORIGINS=http://localhost:3000
networks:
- temporal-network
ports:
- "8080:8080"
networks:
temporal-network:
driver: bridge
name: temporal-network
volumes:
postgres_data:Verifizieren: docker compose config --quiet darf keinen Fehler ausgeben. Wenn der Befehl ohne Ausgabe endet, ist die YAML-Syntax gültig.
Schritt 4: Stack starten
Starte alle Dienste im Hintergrund:
docker compose up -dDer Start-Vorgang dauert je nach System und Download-Geschwindigkeit 2–5 Minuten. Die Reihenfolge ist vollständig durch depends_on-Bedingungen gesteuert: PostgreSQL muss gesund sein, bevor das Schema angelegt wird; der Temporal-Server wartet auf den erfolgreichen Abschluss des Schema-Setups; UI und Namespace-Container warten auf einen gesunden Server.
Logs aller Dienste live verfolgen:
docker compose logs -fVerifizieren: Nach ca. 2–3 Minuten zeigt docker compose ps folgendes Bild:
NAME STATUS
temporal-postgresql Up (healthy)
temporal-admin-tools Exited (0)
temporal Up (healthy)
temporal-create-namespace Exited (0)
temporal-ui UpBeide admin-tools-Container müssen mit Exit-Code 0 beendet sein (nicht 1 oder laufend). Der Temporal-Server und PostgreSQL müssen den Status healthy tragen. Ein anderer Exit-Code bei den Admin-Tools deutet auf ein Datenbankverbindungsproblem oder fehlende Skript-Dateien hin – prüfe dann mit docker compose logs temporal-admin-tools.
Schritt 5: Web-UI aufrufen und Namespace prüfen
Öffne http://localhost:8080 im Browser. Die Temporal Web-UI zeigt das Dashboard mit dem Namespace default. Unter Namespaces in der linken Navigation sollte „default" als aktiver Namespace gelistet sein. Ohne diesen Namespace können keine Workflows gestartet werden – sein Fehlen ist der häufigste Fehler bei der Erstinstallation.
Zusätzlich kannst du per curl prüfen, ob die UI antwortet:
curl -sI http://localhost:8080 | head -1Erwartete Ausgabe: HTTP/1.1 200 OK oder HTTP/1.1 301 Moved Permanently.
Verifizieren: Browser zeigt das Temporal-Dashboard ohne Fehlermeldung „Unable to reach server". Namespace „default" ist unter Settings > Namespaces sichtbar. Der gRPC-Port ist erreichbar:
docker exec temporal nc -z localhost 7233 && echo "gRPC OK"Erwartete Ausgabe: gRPC OK.
Schritt 6: Ersten Worker/Client verbinden
Dein Worker oder Client verbindet sich immer über Port 7233 des Docker-Hosts, Namespace default. Hier ein Verbindungsbeispiel für die gängigsten SDKs:
# Go: client.Dial(client.Options{HostPort: "localhost:7233"})
# Python: await Client.connect("localhost:7233")
# TypeScript: new Client({ connection: { address: "localhost:7233" } })
# Namespace ist immer "default" (ohne explizite Angabe bereits Standard)Läuft dein Worker in einem anderen Docker-Container im selben Netzwerk (temporal-network), lautet die Adresse temporal:7233 statt localhost:7233. Füge dazu den Worker-Container dem Netzwerk temporal-network in der compose.yaml hinzu.
Einen zusätzlichen Namespace kannst du jederzeit anlegen:
docker exec temporal-admin-tools temporal operator namespace create mein-namespace --address temporal:7233Verifizieren: Starte einen einfachen Worker und prüfe, ob er sich ohne Verbindungsfehler registriert. In docker compose logs temporal sollten keine ERROR-Einträge bezüglich fehlender Verbindungen erscheinen.
Schritt 7: Updates durchführen
Neue Temporal-Versionen erscheinen regelmäßig. Beim Update müssen Server- und Admin-Tools-Version immer identisch bleiben. Aktualisiere zuerst die Versionsnummern in der .env, dann:
# .env anpassen: TEMPORAL_VERSION, TEMPORAL_ADMINTOOLS_VERSION, TEMPORAL_UI_VERSION
docker compose pull
docker compose up -dDas Schema-Update übernimmt der temporal-admin-tools-Container beim nächsten Start automatisch, sofern das Setup-Skript aufgerufen wird. Lese vor jedem Major-Versions-Update die offiziellen Release-Notes, da Datenbankmigrationen manchmal besondere Schritte erfordern.
Verifizieren: docker compose ps zeigt alle Dienste mit neuen Image-Tags. docker compose logs temporal | grep version gibt die neue Serverversion aus.
Troubleshooting / Typische Fehler
- „connection refused" oder „Unable to reach server" in der UI: Die Variable
BIND_ON_IP=0.0.0.0fehlt imtemporal-Dienst. Ohne sie lauscht der Server nur auf127.0.0.1und ist von anderen Containern nicht erreichbar. Ergänzen unddocker compose up -derneut ausführen. - „failed to load dynamic config": Das Verzeichnis
./dynamicconfigexistiert nicht oder enthält keinedevelopment-sql.yaml. Lösung:ls dynamicconfig/prüfen – die Datei muss vorhanden sein, bevordocker compose upaufgerufen wird. Sie kommt aus dem geklontensamples-server-Repository. - admin-tools beendet sich mit Exit-Code 1: Das Skript
./scripts/setup-postgres.shexistiert nicht oder ist nicht ausführbar. Sicherstellen, dass das Repository vollständig geklont wurde und derscripts/-Ordner relativ zurcompose.yamlliegt. - „namespace not found" beim Workflow-Start: Der
temporal-create-namespace-Container ist mit einem Fehler beendet oder wurde übersprungen. Mitdocker compose logs temporal-create-namespaceprüfen und ggf. manuell anlegen:docker exec temporal-admin-tools temporal operator namespace create default --address temporal:7233. - Port-Konflikt auf 5432: Läuft lokal bereits PostgreSQL, ist der Port belegt. Den Host-Port in der
compose.yamlauf5433:5432ändern – der interne Docker-Port 5432 bleibt gleich. - Schema-Inkompatibilität nach Update: Tritt auf, wenn
TEMPORAL_VERSIONundTEMPORAL_ADMINTOOLS_VERSIONunterschiedliche Werte haben. Immer beide Versionen synchron halten. - Veraltetes
auto-setup-Image verwendet: Viele Online-Tutorials zeigen nochtemporalio/auto-setup. Dieses Image ist offiziell als deprecated markiert und erhält keine Sicherheitsupdates mehr. Immer auftemporalio/server+temporalio/admin-toolswechseln. - Workflow-History-Limit überschritten: Ein einzelner Workflow darf standardmäßig maximal 50.000 History-Events haben. KI-Agenten-Loops mit Tausenden von Schritten erreichen dieses Limit. Lösung: das Continue-as-New-Pattern im Workflow-Code implementieren.
- Datenverlust nach docker compose down: Nur bei fehlendem Named Volume. Mit dem
postgres_data-Volume in der Compose-Datei bleiben alle Daten erhalten – beim nächstendocker compose upwerden sie automatisch wiederhergestellt.
Häufige Fragen
Kann ich Temporal für Produktions-Workloads mit Docker Compose betreiben?
Für kleine bis mittlere Deployments auf einem einzelnen Server: ja. Temporal selbst empfiehlt jedoch offiziell Kubernetes mit Helm-Charts (ab v0.73.1+ für Server 1.30+) für hohe Verfügbarkeit und horizontale Skalierung. Docker Compose ist ideal für Entwicklungsumgebungen, CI/CD-Pipelines und kleine Produktivinstallationen mit moderaten Workload-Anforderungen.
Wozu brauche ich Elasticsearch?
Nur für erweiterte Workflow-Sichtbarkeit: komplexe Such- und Filterabfragen über eigene Workflow-Attribute (sogenannte Search Attributes). Für die meisten Anwendungen reicht die PostgreSQL-only-Variante völlig aus. Elasticsearch erhöht den RAM-Bedarf auf mindestens 8 GB und macht den Stack deutlich komplexer.
Was ist der Unterschied zwischen Workflows und Activities?
Workflows sind deterministischer Orchestrierungs-Code: kein direkter Netzwerkzugriff, keine Zufallszahlen, keine Systemzeit (datetime.now()). Activities sind die eigentlichen Arbeitseinheiten – API-Aufrufe, Datenbankabfragen, Berechnungen – und können beliebig oft automatisch wiederholt werden. Dieser Trennung muss konsequent gefolgt werden: Alles mit Seiteneffekten gehört in Activities, der Ablauf gehört in Workflows.
Wie prüfe ich, ob Temporal korrekt läuft?
Drei unabhängige Checks: (1) docker compose ps – alle Dienste außer den beiden Admin-Tools sollten running/healthy sein, die Admin-Tools Exited (0). (2) curl -sI http://localhost:8080 | head -1 – Web-UI muss mit HTTP 200 antworten. (3) docker compose logs temporal | grep -E "Frontend started|Start service" – diese Meldungen bestätigen einen sauberen Server-Start.
Wie erstelle ich weitere Namespaces?
Über die Kommandozeile: docker exec temporal-admin-tools temporal operator namespace create mein-namespace --address temporal:7233. Alternativ über die Web-UI unter Settings > Namespaces > Create Namespace.
Unterstützen die offiziellen Images Apple Silicon (M1/M2)?
Ja, ab Temporal-Version 1.14.2+ werden die Images auch für linux/arm64 bereitgestellt. Auf einem Mac mit Apple Silicon läuft der Stack damit nativ ohne Rosetta-Emulation.
Fazit
Temporal löst ein echtes Problem in verteilten Systemen: fehlertolerante, zustandsbehaftete Workflows ohne selbst geschriebene Retry-Logik, Queuing-Infrastruktur oder Zustandsdatenbanken. Der Einstieg via Docker Compose ist überschaubar – das samples-server-Repository liefert alle nötigen Bausteine. Entscheidend sind drei Punkte: BIND_ON_IP=0.0.0.0 im Server-Container, das vorhandene dynamicconfig-Verzeichnis vor dem ersten Start und die identischen Versionen von Server und Admin-Tools. Wer Temporal produktiv mit hoher Last betreibt, sollte mittelfristig Kubernetes mit den offiziellen Helm-Charts evaluieren – für KMU-Szenarien und die Entwicklungsumgebung ist Docker Compose aber ein ausgezeichneter, schlanker Einstieg.
Für eine sichere Produktivumgebung empfiehlt sich die Absicherung des Docker-Stacks: Docker Compose absichern: Secrets, Healthchecks und Non-Root zeigt, wie du Passwörter aus der Compose-Datei herausnimmst und Container mit minimalen Rechten betreibst. Und weil Temporal auf PostgreSQL aufbaut: MySQL & PostgreSQL Backup automatisieren erklärt, wie du das postgres_data-Volume regelmäßig per pg_dump sicherst und in die Cloud überträgst.
Weiterführende Anleitungen und Quellen
- Docker Compose: Multi-Container-Stacks aufbauen – Grundlagen zu Compose-Syntax, depends_on und Healthchecks
- Docker-Netzwerke und Volumes richtig nutzen – Named Volumes und Bridge-Netzwerke verstehen
- PostgreSQL pg_dump und pg_restore – Backup und Migration der Temporal-Datenbank
- Windmill mit Docker installieren – alternative Developer-Workflow-Plattform im Vergleich
- n8n mit Docker: KI-Workflows und Automatisierung self-hosted – Low-Code-Automatisierung als Ergänzung zu Temporal
Offizielle Quellen: Temporalio Samples-Server-Repository (github.com/temporalio/samples-server/tree/main/compose), Temporal Self-Hosted Deployment Guide (docs.temporal.io/self-hosted-guide/deployment), Docker Hub: temporalio/server und temporalio/ui.