Stop Volume-Mounting Code Into Containers

Compose watch mode syncs files, rebuilds on dependency changes, and restarts on config edits — all without bind mounts.

Share

Every developer who runs services in Docker has the same local dev ritual. You bind-mount your source directory, install a file watcher inside the container, and hope hot reload picks up the changes. On macOS and Windows, you cross your fingers that file events even arrive.

There's a better way that's been sitting in Compose since version 2.22, and almost nobody uses it.

Why this matters

Bind mounts create three recurring headaches: permission mismatches between host and container, unreliable change notifications on macOS and Windows, and no way to differentiate between "a source file changed" and "package.json changed so I need a full rebuild."

You end up with a soup of nodemon, custom shell scripts, and manual docker compose restart calls. Compose watch replaces all of that with a declarative block that lives right next to your service definition.

How it works

You define a develop.watch section in your docker-compose.yml. Each rule maps a host path to an action:

  • sync — copies the changed file into the running container. Ideal for source code where your framework handles hot reload.
  • rebuild — tears down and rebuilds the image, then recreates the container. Use this for dependency manifests.
  • sync+restart — copies the file and restarts the container process. Perfect for config files the app reads at startup.

Run docker compose watch and Compose monitors your host filesystem, applying the right action to each change.

Where this helps

Source files: Map your src/ directory with sync. Your framework's HMR works as if the files were local — no volume mount needed.

Dependency changes: Point rebuild at package.json, Cargo.toml, or go.mod. The image rebuilds and reinstalls dependencies automatically.

Config files: Use sync+restart for YAML or .env files. The new config lands and the process picks it up on restart.

Watch out

The rebuild action is expensive. Don't point it at broad directories — be specific. A path like ./requirements.txt keeps rebuilds surgical.

sync only pushes files into the container. Files generated inside the container won't appear on your host, and deletion edge cases exist. If you need bidirectional sync — build artifacts, uploaded files — keep a bind mount for that specific path.

You'll need Docker Compose 2.22 or later. Most Docker Desktop installs are past that, but CI environments or older Linux servers may lag.

Try it yourself

# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "3000:3000"
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./package.json
        - action: sync+restart
          path: ./config/settings.yaml
          target: /app/config/settings.yaml

# Then start dev with:
# docker compose watch

TL;DR

  • What changed: Compose 2.22+ adds declarative file watching with sync, rebuild, and sync+restart actions.
  • Why it matters: Eliminates bind-mount headaches, custom file watchers, and manual restarts during local development.
  • What to try today: Replace your source bind mount with a develop.watch config and run docker compose watch.