Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Datenbanken 03.08.2026 · 12 min Lesezeit

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

Historische Windmühle am Wasser bei Sonnenuntergang, leuchtende Leitungen führen zu kleinen Serverschränken im Gras

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

  1. Linux-Host oder VM mit Docker Engine 24 oder neuer und Compose-Plugin (docker compose). Einrichtung: Docker und Docker Compose auf Linux installieren.
  2. Mindestens 2 vCPUs und 4 GB RAM, x86-64 oder ARM64; für mehrere parallele Jobs 4 vCPUs und 8 GB RAM.
  3. Freier Port 80 (Caddy); Port 25 nur, wenn Sie E-Mail-Trigger nutzen.
  4. Internetzugang für Images von ghcr.io und Docker Hub.
  5. Mindestens 5 GB freier Speicher, produktiv 20 GB (der Abhängigkeits-Cache wächst mit den genutzten Sprachen).
  6. 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:

ServiceImageAufgabe
dbpostgres:18Einziger Zustandsspeicher (Scripts, Jobs, Workflows)
windmill_serverghcr.io/windmill-labs/windmill:1.855.1API + Web-Frontend (Port 8000 intern)
windmill_worker × 3ghcr.io/windmill-labs/windmill:1.855.1Job-Ausführung, sandboxed, skalierbar
windmill_worker_nativeghcr.io/windmill-labs/windmill:1.855.1Leichte In-Process-Jobs (HTTP, SQL); benötigt wenig RAM
windmill_extraghcr.io/windmill-labs/windmill-extra:latestLSP (Sprachintelligenz im Editor) + Debugger
windmill_indexerghcr.io/windmill-labs/windmill:1.855.1Volltextsuche (Enterprise Edition), standardmäßig 0 Replikate
caddyghcr.io/windmill-labs/caddy-l4:2.11.4-1Layer-4-Reverse-Proxy; HTTP auf Port 80, SMTP-Weiterleitung
VolumeInhalt
db_dataPostgreSQL-Daten, also alles, was Windmill weiß
worker_dependency_cachepip/npm/Go-Module-Cache; verhindert wiederholtes Installieren
worker_logsGeteilter Log-Speicher für Server und Worker
lsp_cachePython-LSP-Cache für den Web-Editor
windmill_indexSuchindex des Indexers
caddy_dataTLS-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 .env

Verifizieren: 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=10

Ersetzen 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:

  1. Port 80 belegt: In caddy 80:80 durch z. B. 8080:80 ersetzen; weitere Änderungen sind nicht nötig.
  2. Keine E-Mail-Trigger: Zeile 25:25 entfernen, damit Port 25 nicht offen ist.
  3. Ohne privileged: Bei windmill_worker privileged: true entfernen und FAVOR_UNSHARE_PID=true durch ENABLE_UNSHARE_PID=false ersetzen. Die PID-Namespace-Isolation entfällt dann.
  4. Mehr Worker: replicas beim windmill_worker etwa 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 ps

Erwartete 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              Up

Server 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_server

Verifizieren: 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:

  1. E-Mail: admin@windmill.dev
  2. 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:

  1. Base URL: die Adresse, unter der Windmill erreichbar ist (wichtig für E-Mail-Links und OAuth).
  2. Windmill Hub Sync: optional, synchronisiert Community-Scripts aus dem öffentlichen Hub.
  3. Workers: Die Gruppen default und native erscheinen 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:

  1. einen HTTP-Endpunkt erstellen (das Script wird zur REST-API),
  2. einen Cron-Trigger einrichten (z. B. täglich 02:00 Uhr),
  3. 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:25

Der 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 caddy

Verifizieren: 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 -d

Laufende 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/null

Automatische 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

  1. 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), dann docker compose up -d caddy.
  2. bind: address already in use für Port 80: Ein anderer Webserver belegt Port 80. Caddy-Port auf 8080:80 ändern.
  3. FATAL: password authentication failed for user "postgres": DATABASE_URL und POSTGRES_PASSWORD passen nicht zusammen, oder die Compose-Datei enthält noch POSTGRES_PASSWORD: changeme. Es wirkt nur beim ersten Start mit leerem Volume; ein bestehendes Passwort ändern Sie per ALTER USER.
  4. Worker crashen mit clone(CLONE_NEWPID) operation not permitted: privileged: true fehlt. Ergänzen oder wie in Schritt 3 ENABLE_UNSHARE_PID=false setzen.
  5. service "windmill_server" has neither an image nor a build context specified beim Start: Die Variable WM_IMAGE ist nicht gesetzt, davor warnt Compose mit The "WM_IMAGE" variable is not set. Defaulting to a blank string. Prüfen Sie, ob die .env im selben Verzeichnis wie compose.yaml liegt und WM_IMAGE enthält.
  6. Jobs dauern sehr lange (Python/npm): Das Volume worker_dependency_cache:/tmp/windmill/cache fehlt beim windmill_worker.
  7. Jobs landen nie beim richtigen Worker: WORKER_GROUP falsch oder fehlend. Standard-Worker: default, nativer Worker: native; unter Workers prüfen.
  8. Datenbank-Container beendet sich nach Update von PostgreSQL 16: postgres:18 liest kein Volume von PostgreSQL 16. Bei postgres:16 mit /var/lib/postgresql/data bleiben oder nach „Upgrade PostgreSQL to 18“ der Windmill-Doku migrieren.
  9. Externe Datenbank (RDS/Azure) schlägt beim Start fehl: Meldung role "windmill_admin" does not exist: init-db-as-superuser.sql aus 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

  1. Docker und Docker Compose auf Linux installieren: die Grundlage für alle Docker-Stacks
  2. MySQL & PostgreSQL Backup automatisieren mit cron: Windmills PostgreSQL-Datenbank sichern
  3. Caddy als Reverse Proxy einrichten: HTTPS und automatische Zertifikate verstehen
  4. Temporal mit Docker installieren: die Alternative für komplexe Durable Workflows
  5. 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).