Windmill mit Docker installieren: Scripts zu APIs, Workflows und UIs
Windmill macht Python-, TypeScript-, Go- und SQL-Scripts zu APIs, Hintergrund-Jobs und internen Oberflächen. Die Anleitung zeigt den offiziellen Docker-Stack mit fester Version, eigenem Datenbankpasswort, HTTPS und Backup.
Geprüft am 31.10.2026 · für Windmill 1.855.1
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

Windmill ist eine Open-Source-Plattform, die Scripts in Python, TypeScript, Go, Bash oder SQL als getaktete Jobs, HTTP-Endpunkte, mehrstufige Workflows oder interne Formulare bereitstellt. Den Zustand hält allein PostgreSQL. Diese Anleitung richtet den offiziellen Docker-Stack auf einem Linux-Server ein.
Voraussetzungen
- Linux-Host oder VM mit Docker Engine 24 oder neuer und Compose-Plugin (
docker compose). Einrichtung: Docker und Docker Compose auf Linux installieren. - Mindestens 2 vCPUs und 4 GB RAM, x86-64 oder ARM64; für mehrere parallele Jobs 4 vCPUs und 8 GB RAM.
- Freier Port 80 (Caddy); Port 25 nur, wenn Sie E-Mail-Trigger nutzen.
- Internetzugang für Images von
ghcr.iound Docker Hub. - Mindestens 5 GB freier Speicher, produktiv 20 GB (der Abhängigkeits-Cache wächst mit den genutzten Sprachen).
- Für HTTPS: eine Domain mit A-Record auf den Server und offene Ports 80 und 443. Hintergrund: Caddy als Reverse Proxy einrichten.
Architektur auf einen Blick
Der Stack besteht aus diesen Diensten und Volumes:
| Service | Image | Aufgabe |
|---|---|---|
| db | postgres:18 | Einziger Zustandsspeicher (Scripts, Jobs, Workflows) |
| windmill_server | ghcr.io/windmill-labs/windmill:1.855.1 | API + Web-Frontend (Port 8000 intern) |
| windmill_worker × 3 | ghcr.io/windmill-labs/windmill:1.855.1 | Job-Ausführung, sandboxed, skalierbar |
| windmill_worker_native | ghcr.io/windmill-labs/windmill:1.855.1 | Leichte In-Process-Jobs (HTTP, SQL); benötigt wenig RAM |
| windmill_extra | ghcr.io/windmill-labs/windmill-extra:latest | LSP (Sprachintelligenz im Editor) + Debugger |
| windmill_indexer | ghcr.io/windmill-labs/windmill:1.855.1 | Volltextsuche (Enterprise Edition), standardmäßig 0 Replikate |
| caddy | ghcr.io/windmill-labs/caddy-l4:2.11.4-1 | Layer-4-Reverse-Proxy; HTTP auf Port 80, SMTP-Weiterleitung |
| Volume | Inhalt |
|---|---|
| db_data | PostgreSQL-Daten, also alles, was Windmill weiß |
| worker_dependency_cache | pip/npm/Go-Module-Cache; verhindert wiederholtes Installieren |
| worker_logs | Geteilter Log-Speicher für Server und Worker |
| lsp_cache | Python-LSP-Cache für den Web-Editor |
| windmill_index | Suchindex des Indexers |
| caddy_data | TLS-Zertifikate (bei HTTPS-Betrieb) |
Schritt 1: Projektordner anlegen
Legen Sie einen Projektordner an, z. B. /opt/windmill, und laden Sie die drei offiziellen Dateien aus dem Release-Tag. Der Release-Tag hält Compose-Datei, Caddyfile und Image passend zueinander.
sudo mkdir -p /opt/windmill
cd /opt/windmill
V=v1.855.1
sudo curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/$V/docker-compose.yml -o compose.yaml
sudo curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/$V/Caddyfile -o Caddyfile
sudo curl -fsSL https://raw.githubusercontent.com/windmill-labs/windmill/$V/.env -o .envVerifizieren: ls -la /opt/windmill zeigt compose.yaml, Caddyfile und .env. Fehlt das Caddyfile, legt Docker an seiner Stelle ein Verzeichnis an und der Caddy-Container startet nicht.
Schritt 2: .env mit sicheren Zugangsdaten befüllen
Die .env enthält das öffentlich bekannte Passwort changeme. Erzeugen Sie ein eigenes Passwort mit openssl rand -hex 24 (nur Hex-Zeichen, damit die URL gültig bleibt) und passen Sie die Datei an:
sudo nano /opt/windmill/.env# Image für Server und Worker, feste Version statt :main
# Enterprise Edition: ghcr.io/windmill-labs/windmill-ee:1.855.1
WM_IMAGE=ghcr.io/windmill-labs/windmill:1.855.1
# Passwort MUSS mit POSTGRES_PASSWORD übereinstimmen
DATABASE_URL=postgres://postgres:IHR_DB_PASSWORT@db/windmill?sslmode=disable
POSTGRES_PASSWORD=IHR_DB_PASSWORT
# Optionale Log-Rotation
LOG_MAX_SIZE=20m
LOG_MAX_FILE=10Ersetzen Sie IHR_DB_PASSWORT an beiden Stellen durch denselben Wert. POSTGRES_PASSWORD wird erst wirksam, wenn die Compose-Datei ihn liest (Schritt 3).
Verifizieren: grep -E 'POSTGRES_PASSWORD|DATABASE_URL|WM_IMAGE' /opt/windmill/.env zeigt in beiden Passwortzeilen denselben Wert und bei WM_IMAGE die Version 1.855.1.
Schritt 3: compose.yaml prüfen und anpassen
Die offizielle Compose-Datei trägt das Datenbankpasswort fest als POSTGRES_PASSWORD: changeme ein. Ersetzen Sie diese Zeile im Dienst db durch die Variable aus der .env. Die wichtigsten Abschnitte danach (Auszug):
services:
db:
image: postgres:18
shm_size: 1g
restart: unless-stopped
volumes:
- db_data:/var/lib/postgresql
expose:
- 5432
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: windmill
windmill_worker:
image: ${WM_IMAGE}
deploy:
replicas: 3
resources:
limits:
memory: 2048M
privileged: true
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=worker
- WORKER_GROUP=default
- FAVOR_UNSHARE_PID=true
windmill_extra:
image: ghcr.io/windmill-labs/windmill-extra:latest
environment:
- ENABLE_LSP=true
- ENABLE_DEBUGGER=true
- REQUIRE_SIGNED_DEBUG_REQUESTS=true
- WINDMILL_BASE_URL=http://windmill_server:8000
caddy:
image: ghcr.io/windmill-labs/caddy-l4:2.11.4-1
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
ports:
- 80:80
- 25:25
environment:
- BASE_URL=":80"Ab PostgreSQL 18 liegt das Volume unter /var/lib/postgresql, nicht mehr unter .../data. REQUIRE_SIGNED_DEBUG_REQUESTS=true ist der Standard und muss so bleiben: false öffnet einen Debugger ohne Anmeldung. Das Image caddy-l4 ist mit dem Caddyfile desselben Releases abgestimmt; ändern Sie beide nur gemeinsam.
Häufige Anpassungen:
- Port 80 belegt: In
caddy80:80durch z. B.8080:80ersetzen; weitere Änderungen sind nicht nötig. - Keine E-Mail-Trigger: Zeile
25:25entfernen, damit Port 25 nicht offen ist. - Ohne privileged: Bei
windmill_workerprivileged: trueentfernen undFAVOR_UNSHARE_PID=truedurchENABLE_UNSHARE_PID=falseersetzen. Die PID-Namespace-Isolation entfällt dann. - Mehr Worker:
replicasbeimwindmill_workeretwa an die Zahl der vCPUs anpassen.
Verifizieren: docker compose config | grep -E 'POSTGRES_PASSWORD|image:' zeigt Ihr Passwort statt changeme und bei Server und Workern windmill:1.855.1. Eine Warnung, dass version veraltet ist, können Sie ignorieren.
Schritt 4: Stack starten und Images pullen
Starten Sie den Stack im Projektordner. Der erste Start lädt alle Images und dauert je nach Anbindung 2 bis 5 Minuten.
cd /opt/windmill
sudo docker compose up -d
sudo docker compose psErwartete Ausgabe (gekürzt):
NAME STATUS
windmill-db-1 Up (healthy)
windmill-windmill_server-1 Up
windmill-windmill_worker-1 Up
windmill-windmill_worker-2 Up
windmill-windmill_worker-3 Up
windmill-windmill_worker_native-1 Up
windmill-windmill_extra-1 Up
windmill-caddy-1 UpServer und Worker warten, bis db healthy meldet. Hängt ein Container in Restarting, zeigen die Logs die Ursache:
# Alle Dienste
sudo docker compose logs -f
# Einzelne Dienste
sudo docker compose logs db
sudo docker compose logs windmill_serverVerifizieren: docker compose ps zeigt alle acht Container mit Up, db zusätzlich (healthy). Meldet windmill_server password authentication failed, passen DATABASE_URL und POSTGRES_PASSWORD nicht zusammen (Schritt 2 und 3).
Schritt 5: Windmill im Browser öffnen und Ersteinrichtung
Öffnen Sie http://<SERVER-IP> im Browser und melden Sie sich mit den Standard-Zugangsdaten an:
- E-Mail:
admin@windmill.dev - Passwort:
changeme
Windmill führt Sie anschließend durch das Anlegen eines eigenen Administratorkontos. Erledigen Sie das, bevor Sie weiter konfigurieren. In den Instance Settings prüfen Sie danach:
- Base URL: die Adresse, unter der Windmill erreichbar ist (wichtig für E-Mail-Links und OAuth).
- Windmill Hub Sync: optional, synchronisiert Community-Scripts aus dem öffentlichen Hub.
- Workers: Die Gruppen
defaultundnativeerscheinen als aktiv.
Verifizieren: curl -I http://localhost liefert 200 oder eine Weiterleitung auf /user/login. Nach der Anmeldung sehen Sie den leeren Workspace. Meldet der Browser „Verbindung abgelehnt“, prüfen Sie mit docker compose ps, ob caddy läuft.
Schritt 6: Erstes Script anlegen und ausführen
Klicken Sie im Dashboard auf + Script, wählen Sie Python und fügen Sie diesen Code ein:
def main(name: str = "Welt"):
return f"Hallo, {name}!"Klicken Sie auf Test und danach auf Deploy. Ein Worker führt den Code aus; Aus demselben Script können Sie:
- einen HTTP-Endpunkt erstellen (das Script wird zur REST-API),
- einen Cron-Trigger einrichten (z. B. täglich 02:00 Uhr),
- das Script in einen Flow einbauen (mehrere Scripts sequenziell oder parallel).
Verifizieren: Unter Runs steht der Job mit Status „Success“ und dem Ergebnis "Hallo, Welt!". Bleibt der Job in der Warteschlange, läuft noch kein Worker der Gruppe default: docker compose logs windmill_worker prüfen.
Schritt 7: HTTPS und externe Erreichbarkeit (optional)
Mit öffentlicher Domain holt Caddy das Zertifikat selbst. Ändern Sie im Dienst caddy die Variable BASE_URL und veröffentlichen Sie Port 443:
caddy:
environment:
- BASE_URL=windmill.example.com ports:
- 80:80
- 443:443
- 25:25Der A-Record der Domain muss auf den Server zeigen, Ports 80 und 443 müssen in der Firewall offen sein. Tragen Sie die neue Adresse auch als Base URL in den Instance Settings ein. Zur Absicherung des Servers siehe VPS absichern und härten sowie Linux-Server absichern: UFW-Firewall und Fail2ban.
sudo docker compose up -d caddyVerifizieren: curl -I https://windmill.example.com liefert eine Antwort mit gültigem Zertifikat. Im Log (docker compose logs caddy) steht certificate obtained successfully. Schlägt die ACME-Prüfung fehl, ist Port 80 oder 443 von außen nicht erreichbar.
Schritt 8: Updates und Datensicherung
Für ein Update tragen Sie in der .env bei WM_IMAGE die neue Version aus den Release Notes ein und vergleichen die Compose-Datei und das Caddyfile des neuen Release-Tags mit Ihren Dateien. Dann:
cd /opt/windmill
sudo docker compose pull
sudo docker compose up -dLaufende Jobs erhalten bis zu 30 Sekunden zum Abschließen. Die Datenbank-Volumes bleiben erhalten. Ein Wechsel der PostgreSQL-Major-Version (z. B. von 16 auf 18) ist kein normales Update: Das Image startet mit dem alten Cluster nicht und braucht die Migration aus der Windmill-Doku.
Wichtig: Verwenden Sie beim Update nie docker compose down --volumes; das löscht db_data und damit alle Daten.
Für das Backup sichern Sie den gesamten Cluster mit pg_dumpall. Windmill legt neben der Datenbank windmill weitere Datenbanken und clusterweite Rollen an, die ein einzelner pg_dump nicht erfasst:
sudo mkdir -p /opt/windmill/backups
sudo docker compose exec -T db pg_dumpall -U postgres \
| sudo tee /opt/windmill/backups/windmill_$(date +%Y%m%d).sql > /dev/nullAutomatische Rotation und Auslagerung beschreibt MySQL & PostgreSQL Backup automatisieren.
Verifizieren: Nach dem Update zeigt docker compose ps alle Container mit Up, und curl -s http://localhost/api/version nennt die neue Version (im Test CE v1.855.1). Die Sicherungsdatei ist nicht leer und enthält CREATE DATABASE windmill.
Bei allen getesteten Updates von 1.839.0 bis 1.855.1 (18. bis 31.10.2026) waren docker-compose.yml, Caddyfile und .env der Release-Tags identisch, es genügte also der neue Wert für WM_IMAGE. Neue Migrationen spielte der Server jeweils beim Start ein, eine vorher angelegte Variable blieb lesbar, ein Python-Job lief, das falsche Passwort wurde mit 400 abgelehnt, und pg_dumpall enthielt CREATE DATABASE windmill. Zuletzt lief so das Update von 1.848.2 auf 1.849.0 am 26.10.2026: /api/version meldete CE v1.849.0, das Log migration job finished, die Variable und der Python-Job funktionierten wie zuvor. Am 27.10.2026 folgte das Update von 1.849.0 auf 1.850.0 nach demselben Muster: Das Log zeigte die neue Migration ai agent checkpoint und migration job finished, /api/version meldete CE v1.850.0, Variable und Python-Job funktionierten, das falsche Passwort bekam 400. Am 29.10.2026 lief das Update von 1.850.0 auf 1.853.0 ebenso: Das Log zeigte die Migrationen protection rule fork role mapping und ai review instruction resource type, /api/version meldete CE v1.853.0, Variable und Python-Job funktionierten, das falsche Passwort bekam 400. Die Dateien docker-compose.yml, Caddyfile und .env von v1.853.0 sind mit denen von v1.850.0 identisch. Am 30.10.2026 folgte das Update von 1.853.0 auf 1.854.0: Das Log meldete migration job finished, /api/version meldete CE v1.854.0, Variable und Python-Job funktionierten, das falsche Passwort bekam 400. Auch bei v1.854.0 sind die drei Dateien unverändert. Am 31.10.2026 folgte das Update von 1.854.0 auf 1.855.1: Das Log meldete migration job finished, /api/version meldete CE v1.855.1, Variable und Python-Job funktionierten, das falsche Passwort bekam 400. Die drei Dateien von v1.855.1 sind mit denen von v1.854.0 identisch. Laut Release Notes verlangt 1.855.0 für das Signieren von Debug-Sitzungen die Berechtigung zum Programmieren und hält geheime Werte aus flow_env aus gespeicherten Schritt-Eingaben heraus; 1.855.1 zeigt in der Seitenleiste nur noch die eigenen KI-Chats.
Inhaltlich wichtig aus den Release Notes: 1.840.0 warnt beim Start der Worker, wenn WHITELIST_ENVS eine Variable mit geheimnisartigem Namen an Jobs weiterreicht. 1.841.0 bringt eine Liste erlaubter privater Hosts für ALLOW_PRIVATE_MCP_SERVER_URLS, 1.843.0 konfigurierbare Workspace-Rollen, 1.844.0 die Werkzeuge der KI-Sitzungen per MCP unter /api/mcp. 1.847.0 hält auf Wunsch je Worker-Gruppe den Proxy des Workers aus Jobs heraus, 1.848.0 ergänzt schreibgeschützte KI-Sitzungen für Produktion und einen Handler beim Ablauf von Variablen, 1.848.2 verschärft die Isolation in Deno- und Bun-Jobs. 1.849.0 ergänzt eine Anmeldung von Windmill AI bei Anthropic per Workload Identity Federation je Benutzer, 1.850.0 eine Werkzeugfreigabe und ein Rückfrage-Werkzeug für KI-Agenten-Schritte sowie eine Einstellung, die leere Ordner auf der Startseite ausblendet. 1.851.0 bis 1.854.0 bringen unter anderem KI-Prüfungen für Git-Sync-Pull-Requests und Entwürfe, eigenständige KI-Agenten aus KI-Sitzungen und eine Bestätigung des ausführenden Benutzers beim Deploy; 1.854.0 ergänzt einen nur lesenden Secret-Speicher über HTTP und einen Teilen-Dialog und hält WebSocket-Trigger bei Verbindungsabbrüchen verbunden.
Wichtig für PHP-Scripts: Seit 1.843.0 enthalten die Standard-Images windmill und windmill-ee weder PHP noch Composer (PR #11922). Im Test lief ein PHP-Job unter 1.842.0 noch, nach dem Update auf 1.846.1 brach er mit dieser Meldung ab:
execution error:
PHP is not installed on this worker (looked for it at /usr/bin/php). PHP and Composer are no longer included in the standard windmill and windmill-ee images: they ship only in the full images. To run PHP scripts, switch the workers that run PHP jobs to ghcr.io/windmill-labs/windmill-full (Community Edition) or ghcr.io/windmill-labs/windmill-ee-full (Enterprise Edition), or install PHP and Composer on them and point PHP_PATH and COMPOSER_PATH at them.Nutzen Sie PHP-Scripts, betreiben Sie die Worker, die PHP-Jobs ausführen, mit ghcr.io/windmill-labs/windmill-full:1.855.1 (Community Edition) bzw. ghcr.io/windmill-labs/windmill-ee-full:1.855.1 (Enterprise Edition).
Grundlagen zum Betrieb finden Sie in Docker Compose absichern: Secrets, Healthchecks, Non-Root.
Troubleshooting / Typische Fehler
- caddy startet nicht /
no such file or directory: ./Caddyfile: Das Caddyfile fehlt oder wurde von Docker als Verzeichnis angelegt. Verzeichnis entfernen, Caddyfile aus dem Release-Tag laden (Schritt 1), danndocker compose up -d caddy. bind: address already in usefür Port 80: Ein anderer Webserver belegt Port 80. Caddy-Port auf8080:80ändern.FATAL: password authentication failed for user "postgres":DATABASE_URLundPOSTGRES_PASSWORDpassen nicht zusammen, oder die Compose-Datei enthält nochPOSTGRES_PASSWORD: changeme. Es wirkt nur beim ersten Start mit leerem Volume; ein bestehendes Passwort ändern Sie perALTER USER.- Worker crashen mit
clone(CLONE_NEWPID) operation not permitted:privileged: truefehlt. Ergänzen oder wie in Schritt 3ENABLE_UNSHARE_PID=falsesetzen. service "windmill_server" has neither an image nor a build context specifiedbeim Start: Die VariableWM_IMAGEist nicht gesetzt, davor warnt Compose mitThe "WM_IMAGE" variable is not set. Defaulting to a blank string.Prüfen Sie, ob die.envim selben Verzeichnis wiecompose.yamlliegt undWM_IMAGEenthält.- Jobs dauern sehr lange (Python/npm): Das Volume
worker_dependency_cache:/tmp/windmill/cachefehlt beimwindmill_worker. - Jobs landen nie beim richtigen Worker:
WORKER_GROUPfalsch oder fehlend. Standard-Worker:default, nativer Worker:native; unter Workers prüfen. - Datenbank-Container beendet sich nach Update von PostgreSQL 16:
postgres:18liest kein Volume von PostgreSQL 16. Beipostgres:16mit/var/lib/postgresql/datableiben oder nach „Upgrade PostgreSQL to 18“ der Windmill-Doku migrieren. - Externe Datenbank (RDS/Azure) schlägt beim Start fehl: Meldung
role "windmill_admin" does not exist:init-db-as-superuser.sqlaus dem Windmill-Repository mit einer Admin-Rolle ausführen.
Häufige Fragen
Wie viele Worker-Container brauche ich?
Richtwert: ein Standard-Worker pro vCPU mit 1 bis 2 GB RAM, dazu ein nativer Worker. Anpassen über deploy.replicas.
Kann ich Windmill ohne Caddy betreiben?
Ja. Entfernen Sie den Dienst caddy und leiten Sie in Ihrem vorhandenen Reverse Proxy (nginx, Traefik, Nginx Proxy Manager) auf windmill_server:8000 weiter, die Pfade /ws/, /ws_mp/ und /ws_debug/ auf windmill_extra:3000. E-Mail-Trigger (Port 25 auf 2525) müssen Sie dann selbst weiterleiten.
Wie aktiviere ich HTTPS mit einer eigenen Domain?
Wie in Schritt 7. Eigene Zertifikate binden Sie über ein Volume ./certs:/certs und die Zeile tls /certs/cert.pem /certs/key.pem im Caddyfile ein; dann gilt BASE_URL=":443".
Wie aktiviere ich die Enterprise Edition?
Setzen Sie in der .env WM_IMAGE=ghcr.io/windmill-labs/windmill-ee:1.855.1 und starten Sie mit docker compose up -d neu. Den Lizenzschlüssel tragen Sie in den Instance Settings ein. Multiplayer-Bearbeitung aktivieren Sie zusätzlich mit ENABLE_MULTIPLAYER=true im Dienst windmill_extra.
Wie binde ich Scripts aus einem Git-Repository ein?
Über Git Sync in den Workspace-Einstellungen. Änderungen in der Oberfläche landen als Commits im Repository; mit der Windmill-CLI spielen Sie Änderungen aus dem Repository zurück.
Wie funktioniert die Volltext-Suche?
Der Dienst windmill_indexer ist standardmäßig mit replicas: 0 deaktiviert. Die Volltextsuche in Jobs und Logs ist laut offizieller Compose-Datei eine Funktion der Enterprise Edition. Setzen Sie dort replicas: 1 und aktivieren Sie im Caddyfile die Zeile für /api/srch/*.
Fazit
Windmill braucht nur PostgreSQL als Zustandsspeicher und skaliert über die Zahl der Worker. Für stabilen Betrieb zählen feste Versionen, ein eigenes Datenbankpasswort, pg_dumpall als Backup und geplante PostgreSQL-Major-Wechsel. Alternativ nutzt Windmill eine externe PostgreSQL-Instanz, die Sie ohnehin regelmäßig sichern.
Weiterführende Anleitungen und Quellen
- Docker und Docker Compose auf Linux installieren: die Grundlage für alle Docker-Stacks
- MySQL & PostgreSQL Backup automatisieren mit cron: Windmills PostgreSQL-Datenbank sichern
- Caddy als Reverse Proxy einrichten: HTTPS und automatische Zertifikate verstehen
- Temporal mit Docker installieren: die Alternative für komplexe Durable Workflows
- n8n mit Docker: KI-Workflows und Automatisierung self-hosted: Low-Code-Alternative zu Windmill
Offizielle Quellen: Windmill Self-Hosting-Dokumentation und das windmill-labs/windmill-Repository auf GitHub (compose.yaml, .env, Caddyfile).


