Skip to content

Fix: Docker Compose Watch Not Working — sync vs rebuild, Ignore Patterns, WSL/macOS File Events

FixDevs ·

Quick Answer

How to fix docker compose watch errors — develop.watch directive not firing, sync vs sync+restart vs rebuild differences, ignore globs not matching, WSL2 file events delayed, named volumes shadowing watch, and Compose version requirements.

The Error

You add a develop.watch block and docker compose up --watch runs, but file changes don’t propagate:

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

You edit ./src/index.ts and nothing happens in the container.

Or sync+restart triggers a restart loop:

[+] Restarting service "app"
[+] Restarting service "app"
[+] Restarting service "app"

Or --watch exits with:

the docker compose version is too old to support watch

Or watching works on macOS but not on Windows / WSL2:

# Files saved in VS Code on Windows.
# Container's /app/src is unchanged.

Why This Happens

docker compose watch is a developer-experience layer on top of bind mounts. It detects file changes on the host, then performs one of three actions:

  • sync — copy changed files into the running container’s filesystem.
  • sync+restart — sync, then restart the container (for code that runs once at startup).
  • rebuild — rebuild the image and recreate the container (for Dockerfile or build-context changes).

Three sources of pain:

  • path / target mismatch. The path is on your host; target is in the container. A path that doesn’t match a real file location or a target that conflicts with a volume produces silent no-ops.
  • WSL2 and Docker Desktop file events. Editing files on Windows (C:\...) while the container runs against a WSL filesystem (or vice versa) means the file change events cross filesystem boundaries. Some configurations don’t deliver inotify events reliably.
  • Compose version requirements. develop.watch requires Compose v2.22+ and a recent Docker Engine. Older versions silently ignore the section or print the “too old” error.

Fix 1: Verify Compose Version

docker compose version
# Docker Compose version v2.27.0  ← need v2.22 or later

If the version is older, update Docker Desktop (Mac/Windows) or install the latest compose plugin on Linux:

# Linux — install the latest from Docker's apt repo
sudo apt-get update
sudo apt-get install docker-compose-plugin

develop.watch was promoted from experimental in Compose v2.22 (late 2023). Anything older needs an upgrade.

Fix 2: Pick the Right Action for the Change Type

Different file changes need different actions:

services:
  app:
    build: .
    develop:
      watch:
        # Source code that hot-reloads inside the container (Vite, nodemon, etc.):
        - action: sync
          path: ./src
          target: /app/src
          ignore:
            - "**/*.test.ts"
            - "node_modules/"

        # Config files the app reads at startup — sync + restart:
        - action: sync+restart
          path: ./config
          target: /app/config

        # Dockerfile changes need a rebuild:
        - action: rebuild
          path: ./Dockerfile

        # Lockfile changes — rebuild to pick up new deps:
        - action: rebuild
          path: ./package-lock.json

sync alone is what you want for most app source code if your dev process (Vite, Next.js, nodemon, watchexec) hot-reloads on file changes. sync+restart is for code that runs at startup and doesn’t watch its own files.

Pro Tip: Don’t use rebuild for source files. It blows away the container state and is much slower than sync. Reserve rebuild for Dockerfile, package.json (dep changes), or lockfiles.

Fix 3: Match path to Real Locations

path is a directory or file on the host, relative to the compose file:

develop:
  watch:
    - action: sync
      path: ./src          # → ./src/* events
      target: /app/src     # → copied to /app/src inside the container

For multiple discrete paths, list them separately:

develop:
  watch:
    - action: sync
      path: ./apps/web/src
      target: /app/apps/web/src
    - action: sync
      path: ./packages/shared/src
      target: /app/packages/shared/src

The compose file’s directory is the base. ./src means “src relative to docker-compose.yml” — not relative to wherever you ran docker compose up.

Common Mistake: Mapping path: ./src to target: /app (no /src). The sync writes files directly into /app, potentially overwriting files outside the source tree. Always make target mirror the source structure inside the container.

Fix 4: Use ignore to Skip Unwanted Triggers

Without an ignore list, Compose watches every file in path, including node_modules/, .git/, build artifacts, and editor temp files. These often trigger thousands of useless sync events:

develop:
  watch:
    - action: sync
      path: ./
      target: /app
      ignore:
        - "node_modules/"
        - ".git/"
        - "dist/"
        - "*.log"
        - "**/__pycache__/"
        - ".venv/"
        - ".pytest_cache/"

Patterns use gitignore-style globs. node_modules/ matches at any depth; **/__pycache__/ matches in subdirectories explicitly.

Pro Tip: Mirror your .gitignore. If you cat .gitignore and convert each line to a Compose ignore, you’ll catch nearly every false-positive event.

Fix 5: Avoid Conflicts With Bind Mounts and Volumes

If you already have a bind mount and add develop.watch on the same path, the two mechanisms fight each other:

services:
  app:
    volumes:
      - ./src:/app/src      # Bind mount — files are live-shared
    develop:
      watch:
        - action: sync
          path: ./src       # ALSO sync — redundant, can race
          target: /app/src

Pick one:

  • Bind mount only — fastest, native file sharing. Use when host/container filesystems play nicely.
  • develop.watch sync only — copies files via Compose. Use when bind mounts are slow (macOS) or the container needs different file ownership.

For named volumes that shadow your watched path:

services:
  app:
    volumes:
      - node_modules:/app/node_modules   # Named volume — survives restarts
    develop:
      watch:
        - action: sync
          path: ./
          target: /app
          ignore:
            - "node_modules/"            # Don't sync node_modules

Without ignoring node_modules, the watch tries to sync it into the named volume, which doesn’t go well.

Fix 6: WSL2 and File Event Reliability

If you edit files on Windows (C:\code\...) and the container watches a path mounted from \\wsl$\Ubuntu\..., file events can be delayed or dropped. Two safer patterns:

Put the project entirely inside WSL:

# In WSL:
cd ~/code
git clone https://github.com/...
docker compose up --watch

Then edit files via VS Code’s Remote-WSL extension or directly inside WSL. File events stay within one filesystem.

On macOS, prefer Compose watch over bind mounts for large repos:

Bind mounts on macOS go through a virtualization layer and are slow for large file trees. develop.watch sync is one-way (host → container) and faster.

Common Mistake: Mounting Windows paths into Linux containers via C:\ instead of /c/. Docker Desktop translates, but file watcher behavior is inconsistent across CLI vs Docker Desktop UI launches. Stick to WSL paths.

Fix 7: Restart Strategy When sync+restart Loops

A restart loop usually means:

  • The container exits quickly after restart (check docker compose logs app for the actual error).
  • Your code re-saves a watched file at startup (creates a feedback loop).
  • A chmod or chown during startup triggers Compose to see a “change” event.

To diagnose:

docker compose logs --tail 50 app

Look for the last error before the restart. If the container can’t start (missing env var, crash), sync+restart will keep restarting it.

If the issue is your app re-saving a watched file at startup, add it to ignore:

ignore:
  - "src/generated/"
  - "src/version.ts"  # Auto-regenerated on each start

Fix 8: Combine With docker compose up --build --watch

For complex changes (deps + source), launch with both --build and --watch:

docker compose up --build --watch
  • --build rebuilds at startup (catching changes since the last build).
  • --watch keeps watching after.

For CI-friendly one-off runs without watching:

docker compose up -d
docker compose watch  # Detached watch in foreground

Or run watch alongside the existing stack:

docker compose watch app frontend  # Watch only specific services

Still Not Working?

A few less-obvious failures:

  • watch enabled but nothing logs on file change. Run with --verbose: docker compose up --watch --verbose. Look for [watch] lines confirming the directives were parsed.
  • Build context too large during rebuild. Add a .dockerignore. Without it, every rebuild re-uploads node_modules/, .git/, etc. to the daemon.
  • sync succeeds but the container still serves stale files. Your dev server isn’t watching the synced path. Check that Vite/Next/nodemon’s watch root matches the target.
  • Container has wrong file ownership after sync. develop.watch copies as the user the daemon runs as. Set user: in the compose file or USER in the Dockerfile to align with the host UID.
  • macOS: spinning beachball when watching a node_modules-heavy project. Use a named volume for node_modules and add node_modules/ to ignore.
  • develop.watch fires twice for one save. Editor saves write a temp file, then rename — two events. Add *.tmp and editor-specific patterns to ignore.
  • Apple Silicon (ARM) builds slow. Specify platform: linux/arm64 in your service to avoid emulation. rebuild actions go from minutes to seconds.
  • watch works but restart doesn’t actually restart the process. The container has a restart: no policy, and Compose’s restart is in-container. Set restart: unless-stopped for the service, or use command: to wrap your process with a supervisor that restarts on signal.

For related Docker development workflow issues, see Docker Compose env not loaded, Docker Compose service failed to build, Docker volume permission denied, and Docker Compose networking not working.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles