Docker Compose Watch: Das Ende der Container-Rebuild-Hölle

Dreißig Sekunden. So lange habe ich gewartet, bis mein Docker-Container nach einer einzigen Code-Zeile neu gebaut war. Dann nochmal dreißig Sekunden. Und nochmal. Am Ende des Tages hatte ich mehr Zeit damit verbracht, Docker beim Bauen zuzusehen, als tatsächlich Code zu schreiben.

Oder: Wie ich lernte, die Rebuild-Hölle zu überleben und File-Synchronisation zu lieben

Falls du jemals beim fünfzehnten docker compose build in dieser Stunde gemurrt hast “das muss doch besser gehen”, dann wirst du lieben, was Docker 2025 ausgeliefert hat. Der watch-Befehl ist jetzt GA (Generally Available) und wird transformieren, wie du mit Containern entwickelst.

Das Problem: Rebuild-Hölle

Lass uns mal ehrlich sein, wie traditionelle Docker-Entwicklung aussieht:

# Eine Datei bearbeiten
vim src/Controller/UserController.php

# Container neu bauen
docker compose build
# ⏳ Building... (30-60 Sekunden)

# Neustarten
docker compose up
# ⏳ Starting... (10 Sekunden)

# Änderung testen
curl localhost:8000/users
# 🤦 Verdammt, ein Tippfehler

# Repeat ad nauseam

Kennste das? Du bist im Flow, machst schnelle Iterationen, und Docker reißt dich ständig raus mit diesen schmerzhaften Rebuild-Zyklen. Klar, du könntest Volumes mounten und hoffen, dass deine Anwendung Hot Reload kann. Aber das ist fragmentiert, inkonsistent zwischen Frameworks und bricht oft auf subtile Weise mit File-Permissions oder verschachtelten Dependencies.

Der eigentliche Knaller? Du baust komplette Container neu, nur um ein paar geänderte Dateien zu kopieren.

Enter Docker Compose Watch

Docker Compose Watch eliminiert den Rebuild-Zyklus für die Entwicklung. Es überwacht deine Source-Dateien und synchronisiert Änderungen in Echtzeit in laufende Container – keine Rebuilds, keine Restarts (außer du willst sie).

Stand 2025 ist es production-ready, battle-tested und lächerlich einfach einzurichten.

Der Quick Win

Hier ist das minimale Setup, das deine Sanität rettet:

services:
  web:
    build: .
    ports:
      - "8000:8000"
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src

Starten mit:

docker compose watch

Das war’s. Ändere eine Datei in ./src, und Docker synchronisiert sie sofort nach /app/src in deinem Container. Kein Rebuild. Kein Restart. Nur pures, sofortiges Feedback.

Zwei Varianten von Watch

Docker gibt dir zwei Befehle, je nachdem was du sehen willst:

# Zeigt nur Watch-Aktivität (File-Syncs, Rebuilds)
docker compose watch

# Zeigt alles (Watch + alle Container-Logs)
docker compose up --watch

Ich nutze watch, wenn ich an Frontend-Arbeit fokussiert bin und keine Server-Logs brauche. Ich nutze up --watch, wenn ich Full-Stack-Probleme debugge und sowohl File-Änderungen als auch Application-Output sehen muss.

Die drei Watch-Actions

Docker Compose Watch unterstützt drei Synchronisations-Strategien, jede optimiert für unterschiedliche Szenarien.

Action 1: sync (Der schnelle Weg)

develop:
  watch:
    - action: sync
      path: ./src
      target: /app/src

Was es macht: Kopiert geänderte Dateien direkt in den laufenden Container.

Wann nutzen: Application-Code, den dein Framework automatisch hot-reloaded (PHP-Scripts, Python-Module, JavaScript-Dateien).

Geschwindigkeit: Instant. Typischerweise 1-2 Sekunden selbst für große Dateien.

Gotcha: Deine Anwendung muss Dateiänderungen erkennen. Das funktioniert super mit:

  • PHP-FPM (verarbeitet Dateien bei jedem Request)
  • Node.js mit nodemon
  • Python mit watchdog
  • Jedem Framework mit eingebautem Hot Reload

Action 2: rebuild (Die nukleare Option)

develop:
  watch:
    - action: rebuild
      path: package.json

Was es macht: Triggert einen kompletten Container-Rebuild, wenn die Datei sich ändert.

Wann nutzen: Dependency-Dateien oder Konfiguration, die einen Rebuild erfordert (package.json, composer.json, Dockerfile, requirements.txt).

Geschwindigkeit: Gleich wie manueller Rebuild (30-60 Sekunden), aber automatisch.

Warum nützlich: Du bekommst instant Sync für Code-Änderungen und automatische Rebuilds nur wenn nötig. Best of both worlds.

Action 3: sync+restart (Der Mittelweg)

develop:
  watch:
    - action: sync+restart
      path: ./config
      target: /app/config

Was es macht: Synchronisiert die Datei und startet den Main-Process des Containers neu.

Wann nutzen: Konfigurationsdateien, die deine Anwendung nur beim Start liest (Environment-Configs, Service-Definitionen, nginx.conf).

Geschwindigkeit: Schneller Sync + schneller Restart (5-10 Sekunden).

Wichtig: Das startet den Prozess neu, nicht den kompletten Container. Viel schneller als ein Rebuild.

Real-World-Setup: Symfony-Anwendung

Lass uns mal eine komplette Development-Umgebung für eine Symfony-PHP-Anwendung bauen. Hier glänzt Docker Compose Watch richtig.

services:
  php:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - ./:/app
    depends_on:
      - database
    develop:
      watch:
        # Sync PHP-Source-Code sofort
        - action: sync
          path: ./src
          target: /app/src

        # Sync Templates
        - action: sync
          path: ./templates
          target: /app/templates

        # Sync Public-Assets
        - action: sync
          path: ./public
          target: /app/public

        # Rebuild wenn Dependencies sich ändern
        - action: rebuild
          path: composer.json

        - action: rebuild
          path: composer.lock

        # Restart wenn Config sich ändert
        - action: sync+restart
          path: ./config
          target: /app/config

  database:
    image: postgres:16
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: user
      POSTGRES_PASSWORD: secret
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

Was das dir gibt:

  • Controller bearbeiten → instant Sync → Browser refreshen → Änderungen sehen
  • Template bearbeiten → instant Sync → refresh → Änderungen sehen
  • composer.json bearbeiten → automatischer Rebuild → Container bereit mit neuen Dependencies
  • config/services.yaml bearbeiten → Sync + Restart → Container lädt Konfiguration neu

Der Workflow:

# Alles mit Watch starten
docker compose up --watch

# In einem anderen Terminal, normal arbeiten
vim src/Controller/UserController.php
# Watch-Logs zeigen: Syncing src/Controller/UserController.php

# Browser öffnen, Änderungen sofort sehen
# Keine manuelle Intervention nötig

Multi-Service Watch: Der Full Stack

Hier wird’s interessant. Du kannst mehrere Services gleichzeitig watchen, jeder mit eigenen Sync-Regeln.

services:
  # Backend API (PHP/Symfony)
  api:
    build: ./api
    develop:
      watch:
        - action: sync
          path: ./api/src
          target: /app/src
        - action: rebuild
          path: ./api/composer.json

  # Frontend (React/Vite)
  frontend:
    build: ./frontend
    develop:
      watch:
        - action: sync
          path: ./frontend/src
          target: /app/src
        - action: sync
          path: ./frontend/public
          target: /app/public
        - action: rebuild
          path: ./frontend/package.json

  # Worker (Background Jobs)
  worker:
    build: ./worker
    develop:
      watch:
        - action: sync+restart
          path: ./worker/src
          target: /app/src

Alles laufen lassen:

docker compose up --watch

Jetzt hast du:

  • Frontend mit Vite Hot Reload
  • Backend mit PHP File Watching
  • Worker, der bei Code-Änderungen neustartet

Alles automatisch synchronisiert. Das ist die Developer-Experience, die wir die ganze Zeit verdient hätten.

Initial Sync: Das September-2025-Feature

Docker hat im September 2025 ein sneaky nützliches Feature hinzugefügt: initial_sync.

develop:
  watch:
    - action: sync
      path: ./src
      target: /app/src
      initial_sync: true

Was es macht: Synchronisiert alle Dateien sofort, wenn du docker compose watch startest, bevor es auf Änderungen wartet.

Warum es wichtig ist: Wenn du einen Container startest und dann watch startest, könnte dein Container veraltete Dateien haben. Mit initial_sync: true stellt Docker sicher, dass von Anfang an alles synchronisiert ist.

Wann nutzen: Immer. Es gibt keinen Nachteil, und es verhindert komische “warum läuft mein Container mit altem Code?”-Bugs.

Profiles: Umgebungsbasierte Services

Während wir dein Docker-Setup modernisieren, lass uns mal über Compose Profiles reden. Die sind perfekt für konditionale Services basierend auf deiner Umgebung.

services:
  # Läuft immer
  app:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src

  # Nur in Development
  mailhog:
    image: mailhog/mailhog
    profiles: ["dev"]
    ports:
      - "8025:8025"

  # Nur in Development
  adminer:
    image: adminer
    profiles: ["dev"]
    ports:
      - "8080:8080"

  # Nur für Production-ähnliches Testing
  monitoring:
    image: prom/prometheus
    profiles: ["prod"]
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

Development-Workflow:

docker compose --profile dev up --watch

Du bekommst die App, Mailhog für E-Mail-Testing und Adminer für Datenbank-Inspektion.

Production-Simulation:

docker compose --profile prod up

Du bekommst die App und Monitoring, ohne Development-Tools.

Nur die App:

docker compose up

Clean und simple.

Die Gotchas (Was ich auf die harte Tour gelernt habe)

File-Permissions

Docker’s watch synchronisiert Dateien mit den User-Permissions des Containers. Wenn dein Container als www-data läuft, aber deine Host-Dateien deinem User gehören, könntest du Permission-Errors sehen.

Lösung: User-IDs in deinem Dockerfile matchen:

ARG USER_ID=1000
ARG GROUP_ID=1000

RUN groupmod -g ${GROUP_ID} www-data && \
    usermod -u ${USER_ID} -g ${GROUP_ID} www-data

Build mit deinen IDs:

docker compose build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g)

Ignore-Patterns

Watch hat noch keine eingebauten Ignore-Patterns. Wenn du ein Verzeichnis mit node_modules oder vendor synchronisierst, synchronisierst du alles.

Workaround: Sei spezifisch mit Pfaden:

develop:
  watch:
    # ❌ Synchronisiert alles inklusive node_modules
    - action: sync
      path: ./
      target: /app

    # ✅ Synchronisiere nur was du brauchst
    - action: sync
      path: ./src
      target: /app/src
    - action: sync
      path: ./public
      target: /app/public

Volumes vs Watch

Wenn du bereits Volumes gemountet hast, könnte watch redundant erscheinen. Der Unterschied:

Volume Mount: Bidirektional, persistent, teilt sich die gleiche Inode Watch Sync: Einseitig (Host → Container), isoliert, sicherer für node_modules-Konflikte

Du kannst beides nutzen:

services:
  app:
    volumes:
      # Volume für persistente Daten
      - db_data:/var/lib/mysql
    develop:
      watch:
        # Watch für Code-Änderungen
        - action: sync
          path: ./src
          target: /app/src

Multiple Services seit Februar 2025 gefixt

Frühe Versionen von watch hatten Probleme mit mehreren Services. Wenn du Docker Compose 2.25 oder neuer nutzt (Februar 2025 Release), ist das gefixt. Multiple Services mit watch funktionieren tadellos.

Wenn du eine ältere Version hast:

docker compose version

Upgrade, wenn du unter 2.25 bist.

Das Node.js Hot-Reload-Setup

Hier ist ein komplettes Node.js/Express-Beispiel mit echtem Hot Reload:

Dockerfile:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY . .

# Nodemon global installieren für Hot Reload
RUN npm install -g nodemon

CMD ["nodemon", "server.js"]

compose.yaml:

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: development
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src

        - action: sync
          path: ./public
          target: /app/public

        - action: rebuild
          path: package.json

Die Magie: Nodemon erkennt Dateiänderungen im Container und startet den Node-Prozess neu. Watch synchronisiert Dateien vom Host zum Container. Zusammen kreieren sie nahtlosen Hot Reload.

Starten:

docker compose up --watch

Bearbeite irgendeine Datei in ./src, und nodemon startet automatisch neu. Null manuelle Intervention.

Bonus-2025-Features, die du kennen solltest

Während du deinen Docker-Workflow modernisierst, hier sind andere Perlen aus 2025:

CTRL+Z Background-Mode

docker compose up --watch
# Drücke CTRL+Z
# Compose geht in den Hintergrund, kehrt zum Terminal zurück

Keine weiteren Terminal-Fenster mehr öffnen. Das ist chef’s kiss.

Compose Bridge

Konvertiere dein Docker Compose zu Kubernetes:

docker compose bridge convert --format kubernetes

Oder generiere Helm Charts:

docker compose bridge convert --format helm

Nützlich für Staging/Production-Deployments, während du deine lokale Entwicklung in Compose behältst.

Bake als Default-Builder

Für komplexe Builds mit mehreren Plattformen:

services:
  app:
    build:
      context: .
      platforms:
        - linux/amd64
        - linux/arm64

Docker nutzt jetzt BuildKit Bake unter der Haube, was Multi-Platform-Builds signifikant schneller macht.

Die Transformation

Lass uns mal drüber reden, was das wirklich für deinen Tag bedeutet.

Vor Docker Compose Watch:

  • Datei bearbeiten
  • 30-60 Sekunden auf Rebuild warten
  • 10 Sekunden auf Restart warten
  • Änderung testen
  • 50-100 Mal pro Tag wiederholen
  • Verschwendete Zeit: Stunden

Nach Docker Compose Watch:

  • Datei bearbeiten
  • 1-2 Sekunden auf Sync warten
  • Änderung testen
  • Unendlich wiederholen
  • Verschwendete Zeit: Minuten

Die Zahlen sind wichtig, aber das Gefühl ist wichtiger. Du bleibst im Flow. Du machst keinen Context-Switch zu Twitter während du auf Builds wartest. Du verlierst deinen Gedankenfaden nicht.

Wann Watch NICHT nutzen

Lass uns ehrlich über die Limitierungen sein:

Production: Niemals. Watch ist ein Development-Tool. Deine Production-Container sollten immutable sein, einmal gebaut, überall deployed.

CI/CD: Nope. Deine CI-Pipeline sollte jedes Mal saubere Images from scratch bauen.

Compilierte Sprachen mit komplexen Builds: Wenn deine Go/Rust/Java-Anwendung 5 Minuten zum Kompilieren braucht, wird watch nicht bei der Kompilierung selbst helfen. Du brauchst immer noch den Build-Step. Aber watch kann Source-Dateien syncen und Rebuilds automatisch triggern.

Shared Volumes funktionieren gut: Wenn du mit Volume-Mounts zufrieden bist und sie für dein Setup funktionieren, ändere nichts. Watch glänzt, wenn Volumes Probleme kreieren (node_modules-Konflikte, Performance auf Mac/Windows, Permission-Issues).

Die Alternativen (Und warum Watch gewinnt)

Volume Mounts

Pros:

  • Simple, gibt’s schon ewig
  • Keine spezielle Konfiguration

Cons:

  • Bidirektional (Container kann Host-Dateien modifizieren)
  • Performance-Probleme auf Mac/Windows
  • node_modules-Konflikte machen dich wahnsinnig
  • Permission-Probleme

Verdict: Super für simple Cases, schmerzhaft im Scale

docker-sync

Pros:

  • Löst Mac/Windows-Performance
  • Reifes Ecosystem

Cons:

  • Third-Party-Tool
  • Extra Daemon zum Laufen bringen
  • Komplexe Konfiguration

Verdict: War notwendig, jetzt obsolet mit watch

Manuelle Rebuild-Scripts

Pros:

  • Totale Kontrolle

Cons:

  • Du maintainst Custom-Tooling
  • Fehleranfällig
  • Jeder im Team muss dein Setup kennen

Verdict: Hör auf, das Rad neu zu erfinden

Docker Compose Watch

Pros:

  • Native Docker-Feature
  • Null Dependencies
  • Deklarative Konfiguration
  • Funktioniert plattformübergreifend
  • Maintained von Docker, Inc.

Cons:

  • Relativ neu (aber GA in 2025)
  • Keine Ignore-Patterns yet

Verdict: Der moderne Standard

Heute loslegen

Hier ist dein Action-Plan:

1. Docker upgraden:

docker compose version
# Sollte 2.25.0 oder höher sein

2. Watch zu deiner compose.yaml hinzufügen:

develop:
  watch:
    - action: sync
      path: ./src
      target: /app/src

3. Watching starten:

docker compose watch

4. Eine Datei bearbeiten und lächeln:

Beobachte, wie der Sync in 1-2 Sekunden passiert. Spüre die Zeit, die du sparst. Frag dich, warum du das nicht schon früher gemacht hast.

Fazit

Docker Compose Watch ist eines dieser Features, die auf dem Papier klein wirken, aber fundamental deine tägliche Experience verändern. Das Ende der Container-Rebuild-Hölle ist keine ferne Utopie – es ist hier, es ist stabil, und es ist lächerlich einfach zu adoptieren.

Drei Änderungen, die deinen Docker-Development transformieren:

  1. Füge develop.watch-Blöcke zu deinen Services hinzu
  2. Nutze docker compose up --watch statt up
  3. Hör auf, auf Rebuilds zu warten

Probier’s bei deinem nächsten Projekt aus. Besser noch, füge es einem existierenden Projekt hinzu und spüre den Unterschied sofort. Du wirst dich fragen, wie du jemals den alten Weg toleriert hast.

Jetzt entschuldige mich, ich hab Code zu schreiben. Und zum ersten Mal seit Jahren werde ich nicht die Hälfte meines Tages damit verbringen, Docker beim Bauen von Images zuzusehen.


Ressourcen:

Hast du eine clevere Watch-Konfiguration oder ein Gotcha, das ich verpasst habe? Drop es in die Comments. Lass uns zusammen bessere Development-Workflows bauen.