Hot Reload für PHP und Node in Containern einrichten
AI generated
FROM
RUN
Docker · Hot Reload · PHP · Node.js
Hot Reload für PHP und Node
in Containern einrichten

Hot Reload in Containern verhält sich anders als auf dem nackten Host, weil Bind Mounts, Dateisystem-Events und OPcache-Validierung zusätzliche Schichten zwischen Editor und laufendem Prozess einziehen. Wer diese Schichten versteht, bekommt PHP- und Node-Änderungen genauso schnell sichtbar wie ohne Container, ohne bei jeder Codeänderung neu zu bauen.

15 Min. Lesezeit Bind Mounts · OPcache · Polling · Compose Watch PHP-FPM · Vite · Node.js

1. Warum Hot Reload in Containern schwieriger ist

Hot Reload bedeutet, dass eine Codeänderung im Editor ohne manuellen Neustart eines Prozesses sofort in der laufenden Anwendung sichtbar wird. Auf dem nackten Host funktioniert das meist reibungslos, weil Editor, Dateisystem und ausführender Prozess denselben Kernel und dieselbe Dateisystem-Schicht teilen. In einem Container kommen zusätzliche Schichten dazu: der Bind Mount zwischen Host und Container, gegebenenfalls eine virtualisierte Dateisystem-Bridge auf macOS oder Windows, und interne Caching-Mechanismen wie OPcache bei PHP, die Dateiänderungen erst nach einem bestimmten Intervall überhaupt bemerken.

Der häufigste Frust bei Hot Reload in Containern entsteht, wenn eine dieser Schichten übersehen wird. Ein Entwickler ändert eine PHP-Datei, der Browser zeigt aber weiterhin den alten Stand, weil OPcache mit aktivierter Validierung, aber falschem Intervall arbeitet. Oder ein Vite-Devserver im Container bemerkt Dateiänderungen gar nicht, weil inotify-Events durch den Bind Mount nicht zuverlässig durchgereicht werden, besonders auf macOS.

Dieser Artikel geht Schicht für Schicht durch, was für zuverlässigen Hot Reload bei PHP-FPM und Node in Docker-Containern nötig ist, von korrekt konfigurierten Bind Mounts über OPcache-Einstellungen bis zu Polling-basierten Watchern und dem neueren Compose-Watch-Feature.

2. Bind Mounts richtig für PHP-FPM konfigurieren

Die Grundvoraussetzung für Hot Reload bei PHP ist ein Bind Mount, der den Projektordner vom Host direkt in den Container spiegelt, statt den Code beim Build in das Image zu kopieren. Wird der Code stattdessen mit COPY im Dockerfile fest ins Image eingebacken, sieht der laufende Container Änderungen am Host-Code niemals, ganz unabhängig von OPcache oder anderen Einstellungen. Für die Entwicklung gilt deshalb: Quellcode wird immer gemountet, niemals ins Image kopiert.


# docker-compose.yml — development configuration with source code mounted
services:
  php:
    build:
      context: .
      dockerfile: Dockerfile.dev
    volumes:
      - ./src:/var/www/html:cached
      - ./docker/php/opcache-dev.ini:/usr/local/etc/php/conf.d/opcache-dev.ini:ro
    environment:
      PHP_IDE_CONFIG: "serverName=mironsoft-shop"

Die Mount-Option cached ist auf macOS relevant und teilt Docker mit, dass Schreibzugriffe vom Container aus Priorität haben, während Lesezugriffe leicht verzögert propagiert werden dürfen. Für Hot Reload-Zwecke ist diese Verzögerung meist unproblematisch, weil sie im Millisekunden- statt Sekundenbereich liegt und von Nutzern kaum bemerkt wird.

3. OPcache-Validierung für sofortige Codeänderungen

PHP-FPM cacht kompilierten Bytecode standardmäßig über OPcache, was in Produktion die Performance deutlich verbessert, in der Entwicklung aber genau der Grund ist, warum Hot Reload scheinbar nicht funktioniert. Die Einstellung opcache.validate_timestamps steuert, ob PHP bei jedem Request prüft, ob sich die Datei seit dem letzten Caching geändert hat. Ist diese Option deaktiviert, wie in vielen produktionsnahen Konfigurationen üblich, bleiben Codeänderungen bis zu einem manuellen opcache_reset() unsichtbar.


; docker/php/opcache-dev.ini — development-only OPcache settings
; Production should use validate_timestamps=0 with an explicit cache reset on deploy
opcache.enable=1
opcache.validate_timestamps=1
opcache.revalidate_freq=0
opcache.max_accelerated_files=10000

Der Schlüssel opcache.revalidate_freq=0 ist für Hot Reload entscheidend: er sorgt dafür, dass PHP bei jedem einzelnen Request die Timestamp-Validierung durchführt, statt sie nur alle paar Sekunden zu wiederholen. In Produktion würde dieser Wert die Performance messbar verschlechtern, in der lokalen Entwicklung ist der Unterschied bei einem einzelnen Entwickler-Request nicht spürbar, macht aber den entscheidenden Unterschied zwischen sofort sichtbaren und scheinbar hängenden Änderungen.

4. Node, Vite und Polling für Dateisystem-Events

Node-basierte Devserver wie Vite oder Webpack Dev Server nutzen normalerweise native Dateisystem-Events wie inotify unter Linux, um Änderungen sofort zu erkennen. Innerhalb eines Containers mit Bind Mount funktionieren diese Events auf nativem Linux zuverlässig, auf macOS und teilweise unter Windows jedoch nicht immer, weil die zugrunde liegende Dateisystem-Bridge inotify-Events nicht immer korrekt durchreicht. Das Ergebnis: der Devserver merkt Dateiänderungen erst nach einem manuellen Neustart, was Hot Reload praktisch unbrauchbar macht.


{
  "scripts": {
    "dev": "vite --host 0.0.0.0"
  }
}

// vite.config.js — enable polling as a fallback for unreliable fs events
export default {
  server: {
    watch: {
      // Polling trades some CPU usage for reliability across bind mounts
      usePolling: true,
      interval: 300
    },
    host: true,
    hmr: {
      // Explicit port so HMR websocket works through the container's port mapping
      port: 5173,
      clientPort: 5173
    }
  }
}

Die Umgebungsvariable CHOKIDAR_USEPOLLING=true wirkt ähnlich für Tools, die intern die Chokidar-Bibliothek nutzen, etwa ältere Webpack-Setups. Polling erkauft die Zuverlässigkeit von Hot Reload mit etwas höherem CPU-Verbrauch, weil das Dateisystem in festen Intervallen aktiv abgefragt statt passiv auf Events gewartet wird, was für Entwicklungsumgebungen ein akzeptabler Kompromiss ist.

5. Nginx und PHP-FPM ohne Container-Neustart neu laden

Änderungen an PHP-Dateien selbst brauchen dank OPcache-Validierung keinen Neustart, aber Änderungen an der PHP-FPM- oder Nginx-Konfiguration schon, wenn keine Vorkehrungen getroffen werden. Für Hot Reload-freundliche Entwicklung lohnt sich, PHP-FPM mit einem Reload-Signal statt einem vollständigen Neustart zu betreiben, was laufende Requests nicht unterbricht und deutlich schneller ist als ein kompletter Container-Neustart.


# Reload PHP-FPM configuration without dropping active connections
docker compose exec php kill -USR2 1

# Reload nginx configuration the same way
docker compose exec nginx nginx -s reload

Das Signal USR2 an PHP-FPM startet die Worker-Prozesse graceful neu, ohne aktive Requests zu unterbrechen, und übernimmt dabei automatisch geänderte Pool-Konfigurationen. Für den täglichen Hot Reload-Workflow reicht das in den seltenen Fällen, in denen tatsächlich die FPM-Konfiguration selbst geändert wurde, während reine PHP-Codeänderungen ohnehin ohne jeden Neustart auskommen.

6. Docker Compose Watch für automatisches Sync und Rebuild

Seit neueren Docker-Compose-Versionen steht mit develop.watch ein deklaratives Feature zur Verfügung, das Hot Reload ohne zusätzliche externe Tools ermöglicht. Statt eines vollständigen Bind Mounts synchronisiert Compose Watch geänderte Dateien gezielt in den Container, kann bei bestimmten Dateitypen einen automatischen Rebuild auslösen und unterscheidet zwischen reinem Datei-Sync und vollständigem Neubau.


# docker-compose.yml — declarative hot reload with Compose Watch
services:
  app:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /var/www/html
        - action: rebuild
          path: ./composer.json
        - action: sync+restart
          path: ./docker/php/php.ini
          target: /usr/local/etc/php/php.ini

# Start the stack with watch mode enabled
docker compose watch

Die Aktion sync kopiert geänderte Dateien direkt in den laufenden Container, ohne einen Neustart auszulösen, ideal für PHP-Dateien mit aktivierter OPcache-Validierung. Die Aktion rebuild baut das Image komplett neu, sinnvoll bei Änderungen an composer.json oder package.json, während sync+restart eine Datei synchronisiert und danach gezielt den betroffenen Service neu startet. Diese drei Modi decken die meisten Hot Reload-Szenarien ab, ohne dass ein externes Sync-Tool wie Mutagen nötig wäre.

7. Hot Reload auf macOS und Windows in der Praxis

Auf macOS und unter WSL2 auf Windows bringt die virtualisierte Dateisystem-Bridge zwischen Host und Container zusätzliche Latenz mit, die sich direkt auf die gefühlte Geschwindigkeit von Hot Reload auswirkt. Mit aktuellen Mount-Strategien wie virtiofs auf macOS reduziert sich diese Latenz auf ein für die tägliche Entwicklung kaum wahrnehmbares Maß, ältere Mount-Treiber wie osxfs oder gRPC-FUSE führten hier früher zu spürbaren Verzögerungen von mehreren Sekunden.

Wichtig für Hot Reload speziell auf diesen Plattformen: Node-Devserver sollten praktisch immer mit aktiviertem Polling laufen, weil native Dateisystem-Events über die virtualisierte Bridge unzuverlässig ankommen, selbst mit modernen Mount-Treibern. PHP-Änderungen über OPcache-Validierung sind von dieser Problematik weniger betroffen, weil PHP-FPM Dateien bei jedem Request aktiv prüft, statt auf passive Events zu warten.

8. Hot-Reload-Probleme systematisch debuggen

Wenn Hot Reload scheinbar nicht funktioniert, hilft eine systematische Fehlersuche entlang der beteiligten Schichten, statt wahllos Konfigurationswerte zu ändern. Der erste Schritt ist immer, zu prüfen, ob die Dateiänderung überhaupt im Container ankommt, unabhängig von Caching oder Watchern.


#!/usr/bin/env bash
# debug-hot-reload.sh — verify each layer independently
set -euo pipefail

echo "1) Does the change reach the container at all?"
docker compose exec app stat -c '%Y %n' /var/www/html/src/Controller/IndexController.php

echo "2) Is OPcache actually caching (and configured to revalidate)?"
docker compose exec app php -i | grep -E "opcache.(enable|validate_timestamps|revalidate_freq)"

echo "3) Is the Node dev server watching at all, or silently stuck?"
docker compose logs app-frontend --tail=20

echo "4) Are filesystem events reaching the container (Linux only)?"
docker compose exec app-frontend sh -c "command -v inotifywait && inotifywait -m /var/www/html/resources/js"

Diese vier Schritte trennen zuverlässig, ob das Problem beim Bind Mount, bei OPcache, beim Node-Watcher oder bei den zugrunde liegenden Dateisystem-Events liegt. Die meisten Hot Reload-Probleme lassen sich mit dieser Reihenfolge in wenigen Minuten eingrenzen, statt stundenlang Konfigurationsdateien durchzuprobieren.

9. Hot-Reload-Ansätze im Vergleich

Für unterschiedliche Technologien und Plattformen eignen sich unterschiedliche Kombinationen der vorgestellten Techniken.

Szenario Empfohlener Ansatz Latenz CPU-Kosten
PHP auf Linux Bind Mount plus OPcache-Validierung Sehr niedrig Vernachlässigbar
Node auf Linux Native fs-Events, kein Polling nötig Sehr niedrig Niedrig
Node auf macOS/WSL2 Polling aktivieren Niedrig bis mittel Erhöht
Gemischter PHP/Node-Stack Compose Watch mit sync/rebuild/sync+restart Niedrig Moderat

Für reine PHP-Stacks auf Linux ist die Kombination aus Bind Mount und OPcache-Validierung meist ausreichend und kostet praktisch keine zusätzliche Rechenzeit. Node-Devserver auf macOS oder unter WSL2 brauchen dagegen fast immer Polling, um überhaupt zuverlässig zu funktionieren. Compose Watch ist die derzeit flexibelste Lösung für gemischte Stacks, weil es je nach Dateityp unterschiedlich reagieren kann.

Mironsoft

Docker-Entwicklungsumgebungen, PHP-Performance und Node-Tooling

Codeänderungen ohne Wartezeit sehen wollen?

Wir richten zuverlässigen Hot Reload für eure PHP- und Node-Container ein, von OPcache-Feintuning über Compose Watch bis zur plattformspezifischen Polling-Konfiguration für macOS und WSL2.

OPcache-Tuning

Entwicklungsfreundliche PHP-Konfiguration ohne Performanceverlust in Produktion

Compose Watch Setup

Sync-, Rebuild- und Restart-Regeln passend zu eurem Projekt einrichten

Plattform-Optimierung

Mount-Strategien und Polling-Konfiguration für macOS und Windows/WSL2

10. Zusammenfassung

Zuverlässiger Hot Reload in Containern braucht das Zusammenspiel mehrerer Schichten: ein korrekt konfigurierter Bind Mount bringt Codeänderungen überhaupt erst in den Container, OPcache-Validierung mit revalidate_freq=0 sorgt dafür, dass PHP diese Änderungen bei jedem Request erkennt, und Polling überbrückt die Unzuverlässigkeit nativer Dateisystem-Events bei Node-Devservern auf macOS und Windows. Docker Compose Watch bündelt diese Konzepte inzwischen in einem deklarativen Feature direkt in der Compose-Datei.

Wer Hot Reload-Probleme systematisch statt zufällig löst, prüft zuerst, ob die Änderung überhaupt im Container ankommt, dann die Caching-Schicht, dann den Watcher-Prozess. Diese Reihenfolge spart in der Praxis deutlich mehr Zeit als das wahllose Ausprobieren einzelner Konfigurationswerte und macht Hot Reload in Containern genauso schnell wie eine klassische lokale Installation.

Hot Reload für PHP und Node — Das Wichtigste auf einen Blick

Bind Mounts

Quellcode immer mounten, niemals ins Image kopieren, sonst sieht der Container Host-Änderungen nie.

OPcache

validate_timestamps=1 und revalidate_freq=0 in der Entwicklung, niemals in Produktion.

Polling

Für Node-Devserver auf macOS und WSL2 fast immer nötig, da native fs-Events unzuverlässig sind.

Compose Watch

Deklarative sync-, rebuild- und sync+restart-Regeln direkt in der Compose-Datei ohne externe Tools.

11. FAQ: Hot Reload für PHP und Node

1Warum sehe ich PHP-Änderungen nicht sofort?
Meist wegen deaktivierter OPcache-Validierung. validate_timestamps=1 und revalidate_freq=0 lösen das.
2Muss ich immer Polling aktivieren?
Auf Linux meist nicht, auf macOS und WSL2 fast immer nötig für zuverlässiges Hot Reload.
3Unterschied sync vs sync+restart?
sync kopiert ohne Neustart, sync+restart kopiert und startet den betroffenen Service danach neu.
4Warum ist Polling langsamer?
Aktives Abfragen statt passiver Events kostet etwas CPU, funktioniert dafür zuverlässig über Bind Mounts hinweg.
5Darf validate_timestamps=1 in Produktion bleiben?
Technisch ja, kostet aber Performance. validate_timestamps=0 mit explizitem Reset beim Deployment ist besser.
6Wie prüfe ich, ob Änderungen im Container ankommen?
Mit stat direkt im Container prüfen. Bleibt der Zeitstempel gleich, liegt es am Bind Mount, nicht am Caching.
7Funktioniert das auch mit Colima oder Podman?
Ja, die Prinzipien gelten unabhängig von der Container-Runtime, solange Bind Mounts unterstützt werden.
8Braucht Compose Watch eine bestimmte Version?
Ja, eine aktuelle Docker-Compose-Version. Ältere Versionen nutzen manuelles Bind-Mount-Setup als Alternative.
9Warum funktioniert Vite HMR manchmal nicht?
Meist wegen fehlendem hmr.port/clientPort in der Vite-Konfiguration für die WebSocket-Verbindung.
10Verlangsamt OPcache-Validierung die Entwicklung spürbar?
Bei einzelnen Entwickler-Requests nicht spürbar, erst bei Lasttests mit vielen gleichzeitigen Requests relevant.