Hyvä-Watcher und Hot-Reload im lokalen Docker-Setup einrichten
AI generated
Hyvä
phtml
Hyvä · Docker · Tailwind · Lokale Entwicklung
Hyvä-Watcher und Hot-Reload im lokalen Docker-Setup einrichten
vom gespeicherten phtml bis zum sichtbaren Rebuild

Ein funktionierender Hot-Reload-Loop entscheidet im Hyvä-Alltag darüber, ob eine Layout-Anpassung in Sekunden sichtbar wird oder erst nach einem manuellen Deploy-Zyklus. Der Hyvä-Watcher läuft im Hintergrund des Docker-Containers, beobachtet phtml-, CSS- und JS-Dateien über einen Bind-Mount und stößt bei jeder Änderung automatisch einen Tailwind-Rebuild sowie die Regeneration des view_preprocessed-Verzeichnisses an, ganz ohne setup:static-content:deploy. Wer die Mechanik dahinter kennt, inotify-Limits richtig setzt und den Watcher-Status sauber prüfen kann, spart im Entwicklungsalltag täglich Stunden gegenüber dem klassischen Production-Build-Zyklus.

18 Min. Lesezeit bin/start · npm run watch · inotify · Bind-Mounts Mark Shust Docker · Hyvä Theme · Magento 2.4.8

1. Warum ein schneller Hot-Reload-Loop den Hyvä-Alltag verändert

Ohne Hot-Reload sieht der typische Hyvä-Workflow so aus: phtml-Datei ändern, in den Container wechseln, setup:static-content:deploy ausführen, Cache leeren, Browser neu laden und hoffen, dass keine der vorherigen Anpassungen im view_preprocessed-Cache hängen geblieben ist. Bei jeder kleinen CSS-Klasse oder jedem Alpine.js-Attribut wiederholt sich dieser Zyklus, und schon bei zehn Iterationen pro Stunde summiert sich das zu spürbarem Zeitverlust. Der Hyvä-Watcher ersetzt diesen manuellen Deploy-Schritt durch einen automatischen Hintergrundprozess, der genau dieselbe Pipeline auslöst, aber ohne Tastatureingabe und ohne Wartezeit für den vollständigen Static-Content-Deploy.

Der Unterschied zwischen einem Setup ohne Hot-Reload und einem mit korrekt konfiguriertem Watcher lässt sich direkt an Iterationszeit, Anzahl manueller Schritte und Entwicklererfahrung ablesen.

Aspekt Ohne Hot-Reload Mit Hyvä-Watcher Effekt
Iterationszeit pro Änderung 30 bis 90 Sekunden 1 bis 3 Sekunden Sofortiges visuelles Feedback
Manuelle Schritte pro Save deploy, cache:flush, F5 nur F5 Weniger Kontextwechsel
Tailwind-Klassen müssen manuell rebuilt werden automatisch neu erzeugt CSS immer aktuell
view_preprocessed alter Cache-Stand möglich wird automatisch invalidiert Keine Cache-Altlasten
Entwicklererfahrung unterbrochener Flow durchgehender Hot-Reload-Loop Höhere Konzentration

2. Was bin/start im Hintergrund startet

Im Mark-Shust-Docker-Setup übernimmt das Skript bin/start mehr als nur das Hochfahren der Container. Nach dem Start von PHP-FPM, Nginx, MySQL und den übrigen Services prüft das Setup, ob im Theme-Verzeichnis eine package.json mit einem watch-Script existiert, und startet bei Hyvä-Themes zusätzlich einen Hintergrundprozess, der npm run watch innerhalb des Containers ausführt. Dieser Prozess ist der eigentliche Hyvä-Watcher: eine Node-basierte Beobachtung des Theme-Verzeichnisses, die bei jeder relevanten Dateiänderung die Tailwind-Pipeline erneut anstößt.

Wichtig ist, dass dieser Watcher-Prozess dauerhaft im Hintergrund läuft, solange der Container aktiv ist, und nicht bei jedem einzelnen Befehl neu gestartet wird. Ein Neustart des Watchers ist nur nötig, wenn sich Abhängigkeiten ändern, etwa nach der Installation eines neuen npm-Plugins, oder wenn der Prozess durch einen Fehler abgestürzt ist. Für den täglichen Watcher-Betrieb reicht ein einmaliger bin/start, danach läuft der Watcher passiv mit.


#!/usr/bin/env bash
# bin/start (excerpt): Mark Shust docker-magento wrapper
# Starts containers, then boots the Hyvä Tailwind watcher in the background

docker compose up -d

# Wait until PHP-FPM container reports healthy
until docker compose exec -T phpfpm php -v > /dev/null 2>&1; do
  sleep 1
done

# Boot the Hyvä watcher for every Hyvä-based theme found in the project
for theme_path in src/app/design/frontend/*/*/web/tailwind; do
  if [ -f "$theme_path/package.json" ]; then
    echo "[watcher] starting hot reload watcher for $theme_path"
    docker compose exec -T -d phpfpm bash -c \
      "cd /var/www/html/${theme_path#src/} && npm run watch"
  fi
done

echo "Hyvä hot reload watcher is running in the background."

3. Wie der Hyvä-Watcher Änderungen erkennt und Tailwind neu baut

Technisch basiert der Hyvä-Watcher auf dem Tailwind-eigenen Watch-Modus, kombiniert mit einem kleinen Node-Skript, das nach jedem CSS-Rebuild zusätzlich den view_preprocessed-Cache von Magento invalidiert. Der Tailwind-Compiler registriert über das Betriebssystem einen Dateisystem-Watcher auf alle in tailwind.config.js referenzierten Content-Pfade, typischerweise alle *.phtml-Dateien im Theme sowie eigene CSS- und JS-Quellen. Sobald eine dieser Dateien geändert wird, meldet der Kernel ein Dateisystem-Event, Tailwind scannt die geänderte Datei erneut nach genutzten Klassen und schreibt eine aktualisierte styles.css.

Parallel dazu überwacht ein zweiter Watch-Prozess die phtml-Dateien selbst und sorgt dafür, dass Magentos view_preprocessed-Verzeichnis nicht auf einem veralteten Stand verharrt. Für einen zuverlässigen Hot-Reload müssen beide Mechanismen synchron laufen: der CSS-Rebuild durch Tailwind und die Invalidierung des kompilierten Template-Caches. Bleibt einer der beiden Prozesse stehen, wirkt das Frontend im Browser eingefroren, obwohl der Quellcode längst geändert wurde.


{
  "scripts": {
    "watch": "npm-run-all --parallel watch:css watch:preprocessed",
    "watch:css": "tailwindcss -i ./src/tailwind-source.css -o ../web/css/styles.css --watch",
    "watch:preprocessed": "node ./bin/watch-preprocessed.js",
    "build": "NODE_ENV=production tailwindcss -i ./src/tailwind-source.css -o ../web/css/styles.css --minify"
  }
}

4. Docker-Volumes und Bind-Mounts als Voraussetzung

Der beste Hyvä-Watcher nützt nichts, wenn er die Änderungen auf dem Host-Dateisystem gar nicht sieht. Genau hier liegt der kritische Punkt im Docker-Setup: der Watcher läuft innerhalb des PHP-FPM-Containers und beobachtet dort einen Pfad wie /var/www/html/app/design/frontend/Mironsoft/default. Damit inotify-Events aus dem Host-Editor, etwa PhpStorm oder VS Code, überhaupt im Container ankommen, muss das Theme-Verzeichnis als Bind-Mount eingebunden sein, nicht als kopiertes Image-Layer. Bei einem Bind-Mount teilen Host und Container denselben Inode, sodass jede Schreiboperation auf dem Host sofort ein Kernel-Event auslöst, das der Watcher im Container empfängt.

Das Mark-Shust-Setup bringt diese Bind-Mounts bereits standardmäßig für das app-Verzeichnis mit, es lohnt sich aber, die Konfiguration nach individuellen Anpassungen zu prüfen, insbesondere wenn zusätzliche Volumes für eigene Module oder ein separates Theme-Repository eingebunden werden. Fehlt der Bind-Mount, wirkt es so, als würde der Watcher nicht reagieren, obwohl der Prozess läuft, denn technisch bekommt er die Dateisystem-Events des Hosts schlicht nicht zugestellt.


{
  "services": {
    "phpfpm": {
      "volumes": [
        "./src:/var/www/html:cached",
        "phpfpm_sync:/var/www/html_sync:nocopy"
      ]
    }
  }
}

5. inotify-Limits unter Linux und Polling als Fallback

Auch mit korrektem Bind-Mount kann der Hot-Reload-Loop ins Stocken geraten, wenn der Linux-Host zu wenige inotify-Watches erlaubt. Jeder beobachtete Ordner und jede beobachtete Datei belegt einen Eintrag im Kernel-Limit fs.inotify.max_user_watches, und ein Hyvä-Theme mit tausenden phtml-, CSS- und node_modules-Dateien erschöpft den Standardwert vieler Distributionen, der oft nur bei 8192 oder 65536 liegt, sehr schnell. Ist das Limit erreicht, meldet der Watcher-Prozess entweder gar keinen Fehler mehr und reagiert einfach nicht mehr auf neue Änderungen, oder er bricht mit ENOSPC ab.

Die Lösung ist eine dauerhafte Erhöhung des Limits über sysctl. Reicht das aus infrastrukturellen Gründen nicht aus, etwa in Docker Desktop unter WSL2 mit begrenztem Zugriff auf den Host-Kernel, springt Tailwind automatisch oder manuell konfiguriert in den Polling-Modus, der Dateien in festen Intervallen aktiv abfragt statt auf Kernel-Events zu warten. Polling ist langsamer und CPU-intensiver, garantiert aber, dass der Hyvä-Watcher auch dann funktioniert, wenn native inotify-Events nicht zuverlässig ankommen.


# Check current inotify watch limit on the Docker host
cat /proc/sys/fs/inotify/max_user_watches

# Raise the limit permanently
echo "fs.inotify.max_user_watches=524288" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Fallback: force polling mode when native inotify events are unreliable
# (e.g. inside WSL2 or certain remote-filesystem setups)
CHOKIDAR_USEPOLLING=true CHOKIDAR_INTERVAL=300 npm run watch

6. Browser-Refresh: manuell vs. Livereload-Extension

Ein wichtiges Detail, das oft übersehen wird: Hyvä liefert selbst keinen eingebauten Websocket-Reload mit. Der Hyvä-Watcher kümmert sich ausschließlich um den Rebuild von CSS und die Invalidierung des Template-Caches auf Server-Seite, der Browser muss die Seite weiterhin selbst neu laden. Im einfachsten Fall bedeutet das schlicht F5 nach jeder Änderung, sobald man sich an den kurzen Rebuild-Zyklus gewöhnt hat, ist das für viele Entwickler bereits ausreichend für einen produktiven Hot-Reload-Alltag.

Wer den letzten manuellen Schritt ebenfalls automatisieren möchte, kann eine Browser-Extension wie LiveReload nutzen, die per kleinem Snippet oder Browser-Plugin auf Änderungen der ausgelieferten CSS- und JS-Dateien reagiert und den Tab automatisch neu lädt. Da Hyvä dafür keine native Websocket-Verbindung bereitstellt, ist die Extension auf Dateizeitstempel oder einen separaten Proxy angewiesen. Für die meisten Projekte überwiegt der Aufwand einer zusätzlichen Livereload-Infrastruktur den Nutzen gegenüber einem einfachen manuellen F5, gerade weil der eigentliche Rebuild von CSS und Templates ohnehin serverseitig bereits automatisch passiert.

7. Watcher-Status prüfen und neu starten

Bevor man eine ausbleibende Änderung fälschlich dem Bind-Mount oder inotify zuschreibt, lohnt sich zuerst der einfache Check, ob der Hyvä-Watcher-Prozess überhaupt noch läuft. Mit bin/cli ps aux lassen sich alle laufenden Prozesse im PHP-FPM-Container auflisten und nach node oder tailwindcss filtern. Zusätzlich liefert docker logs auf den Container die Konsolenausgabe des Watchers, inklusive der Meldung, welche Datei zuletzt einen Rebuild ausgelöst hat.

Nach der Installation eines neuen npm-Plugins, etwa eines zusätzlichen Tailwind-Plugins oder einer neuen PostCSS-Erweiterung, muss der Watcher-Prozess neu gestartet werden, damit die neue Abhängigkeit tatsächlich geladen wird. Ein einfaches npm install reicht nicht aus, solange der alte Watcher-Prozess mit den zuvor geladenen Modulen weiterläuft. Der Neustart erfolgt entweder durch Beenden des Prozesses und erneutes Ausführen von npm run watch, oder pragmatisch durch einen vollständigen bin/restart des Containers.


# Check whether the Hyvä watcher process is actually running
bin/cli ps aux | grep -E 'tailwindcss|npm run watch'

# Tail the watcher output directly from the container logs
docker logs -f --tail 50 <phpfpm-container-name>

# Restart the watcher after installing a new npm plugin
bin/cli pkill -f "npm run watch" || true
bin/npm --prefix app/design/frontend/Mironsoft/default/web/tailwind install
bin/npm --prefix app/design/frontend/Mironsoft/default/web/tailwind run watch &

8. Vom Hot-Reload-Loop zum Production-Build

Der lokale Hot-Reload-Loop und der Production-Build teilen sich dieselbe Tailwind-Konfiguration, laufen aber mit unterschiedlichen Zielen. Im Watch-Modus optimiert Tailwind für Geschwindigkeit: inkrementelle Rebuilds, keine Minifizierung, kein Purge unbenutzter Klassen über den gesamten Projektumfang. Der Production-Build hingegen läuft einmalig im Rahmen von setup:static-content:deploy, minifiziert das Ergebnis und erzeugt die finalen Assets für pub/static.

Wer diesen Unterschied kennt, vermeidet ein häufiges Missverständnis: ein Deployment, das lokal im Watch-Modus einwandfrei aussah, kann nach dem echten Production-Build anders aussehen, wenn Klassen dynamisch per String-Konkatenation erzeugt wurden und Tailwind sie im finalen Purge-Durchlauf nicht mehr erkennt. Der Hyvä-Watcher ersetzt daher nicht den abschließenden Deploy-Schritt vor einem Release, er beschleunigt ausschließlich die Iteration während der Entwicklung. Vor jedem Livegang bleibt der vollständige Static-Content-Deploy mit Cache-Flush Pflicht.

In der Praxis lohnt sich deshalb eine klare Trennung im Kopf: der Hot-Reload-Loop ist ein reines Entwicklungswerkzeug, das auf dem lokalen Docker-Container läuft und niemals in Richtung Staging oder Produktion synchronisiert wird. Der Production-Build wiederum läuft typischerweise als eigener CI-Schritt, unabhängig vom Watcher-Prozess, und darf sich nicht auf zwischengespeicherte Artefakte aus dem lokalen Watch-Modus verlassen. Wer beide Pipelines sauber trennt, vermeidet, dass ein lokal funktionierender Watch-Stand fälschlich als deploy-fertig missverstanden wird.

9. Typische Fehler und Debugging

Der häufigste Fehler ist ein Watcher, der zwar läuft, aber auf dem falschen Verzeichnis lauscht, etwa weil ein Theme-Fork in einem anderen Vendor-Pfad liegt und die package.json noch auf den alten Pfad zeigt. In diesem Fall zeigt docker logs überhaupt keine neuen Rebuild-Meldungen, obwohl phtml-Dateien aktiv gespeichert werden. Ein zweiter klassischer Fehler ist ein fehlender oder falsch konfigurierter Bind-Mount nach einem Umzug des Projektverzeichnisses, wodurch der Container ein veraltetes Volume mountet und der Watcher auf einem Datenstand von vor Wochen hängen bleibt.

Erreicht das System die inotify-Grenze, äußert sich das oft unauffällig als plötzlich ausbleibender Rebuild ohne Fehlermeldung im Terminal. Ein Blick in dmesg oder in die Watcher-Logs mit aktivierter Debug-Ausgabe hilft, das schnell einzugrenzen. Grundsätzlich gilt: bevor man Tailwind-Konfiguration oder Cache-Einstellungen verdächtigt, lohnt sich zuerst der einfache Dreisatz aus Prozess-Check, Bind-Mount-Kontrolle und inotify-Limit, denn die überwiegende Mehrheit der Watcher-Probleme liegt in genau diesen drei Bereichen.

10. Zusammenfassung

Ein zuverlässiger Hot-Reload-Loop im Hyvä-Theme steht und fällt mit drei Voraussetzungen: einem laufenden Hyvä-Watcher-Prozess, den bin/start automatisch im Hintergrund startet, einem korrekt konfigurierten Bind-Mount, der Dateisystem-Events vom Host in den Container durchreicht, und ausreichend inotify-Watches auf dem Linux-Host. Fehlt eine dieser drei Komponenten, wirkt der Watcher inaktiv, obwohl der Prozess läuft, und die Fehlersuche führt oft in die falsche Richtung.

Der lokale Hot-Reload-Loop ersetzt dabei nie den vollständigen Production-Build vor einem Release: Watch-Modus und Purge-Build verfolgen unterschiedliche Ziele und müssen beide beherrscht werden. Wer Watcher-Status regelmäßig mit bin/cli ps und docker logs prüft, den Watcher nach jeder npm-Installation neu startet und im Zweifel auf Polling-Modus ausweicht, hat die häufigsten Stolpersteine bereits im Griff und profitiert im Alltag von einem Hyvä-Theme, das sich in Sekunden statt Minuten aktualisiert.

Hyvä-Watcher und Hot-Reload: das Wichtigste auf einen Blick

Watcher-Start

bin/start bootet den Hyvä-Watcher automatisch im Hintergrund über npm run watch, kein manueller Zusatzschritt nötig.

Bind-Mount Pflicht

Ohne Bind-Mount empfängt der Watcher keine Host-Änderungen, kopierte Volumes reichen für den Watcher nicht aus.

inotify-Limit erhöhen

fs.inotify.max_user_watches=524288 per sysctl setzen, sonst bricht der Watcher bei großen Themes still ab.

Status prüfen

bin/cli ps aux und docker logs zeigen sofort, ob der Watcher-Prozess wirklich noch läuft.

11. FAQ: Hyvä-Watcher und Hot-Reload im lokalen Docker-Setup

1Was ist der Hyvä-Watcher genau?
Ein Node-Prozess, der npm run watch im Hintergrund ausführt, phtml-, CSS- und JS-Dateien beobachtet und bei Änderungen Tailwind-Rebuild plus Cache-Invalidierung anstößt.
2Wie starte ich den Watcher im Docker-Setup?
bin/start bootet den Watcher-Prozess automatisch im Hintergrund, sofern eine package.json mit watch-Script existiert. Manueller Start nur nach vorherigem Beenden nötig.
3Warum reagiert der Watcher nicht auf Änderungen?
Meist fehlender Bind-Mount oder erschöpftes inotify-Limit. Beides verhindert, dass Host-Events überhaupt beim Container-Prozess ankommen.
4Was bedeutet fs.inotify.max_user_watches?
Kernel-Limit für gleichzeitig überwachte Dateien und Ordner. Große Themes mit node_modules überschreiten den Standardwert schnell und stoppen den Hot-Reload unbemerkt.
5Muss ich nach jeder Änderung deployen?
Nein, der Watcher übernimmt die view_preprocessed-Regeneration automatisch. Der vollständige Static-Content-Deploy bleibt nur vor Production-Releases Pflicht.
6Wie prüfe ich, ob der Watcher läuft?
bin/cli ps aux nach tailwindcss oder node filtern, zusätzlich docker logs auf den PHP-FPM-Container für die laufende Konsolenausgabe.
7Neustart nach npm install nötig?
Ja, ein laufender Watcher hat alte Module bereits geladen und erkennt neue Plugins erst nach explizitem Neustart, manuell oder per bin/restart.
8Gibt es Live-Reload im Browser?
Nicht eingebaut. Hyvä liefert keinen Websocket-Reload, F5 ist Standard, eine Livereload-Extension kann diesen letzten Schritt zusätzlich automatisieren.
9Unterschied zum Production-Build?
Watch-Modus priorisiert Geschwindigkeit ohne Minifizierung, Production-Build erzeugt einmalig minifizierte, purge-optimierte Assets für pub/static.
10Funktioniert das unter WSL2?
Grundsätzlich ja, inotify ist dort aber weniger zuverlässig. CHOKIDAR_USEPOLLING=true als Fallback nutzen, wenn native Events ausbleiben.

Mironsoft

Hyvä-Theme-Entwicklung und Docker-Infrastruktur für Magento 2

Ein Hot-Reload-Loop, der wirklich in Sekunden reagiert?

Wir richten euren Hyvä-Watcher im Docker-Setup korrekt ein, konfigurieren Bind-Mounts und inotify-Limits passend zu eurer Infrastruktur und sorgen dafür, dass der Watcher-Loop im gesamten Entwicklerteam zuverlässig funktioniert.

Docker-Setup-Audit

Bind-Mounts, Volumes und Watcher-Konfiguration im Mark-Shust-Stack prüfen

inotify-Tuning

Kernel-Limits und Polling-Fallback für stabile Watcher-Performance einrichten

Team-Onboarding

Watcher-Workflows dokumentieren, damit jeder Entwickler denselben Watcher-Workflow nutzt