# Docker Compose Watch: Instant Feedback for Local Container Dev

> Docker Compose Watch syncs code into running containers in milliseconds. Here is how I replaced messy bind mounts with sync, rebuild, and sync+restart rules.

URL: https://www.matthewswong.com/en/blog/docker-compose-watch-local-dev/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2026-04-02
- Updated: 2026-04-02
- Category: DevOps

For years my local container workflow had a tax on every change: edit a file, switch to the terminal, run docker compose up --build, wait thirty seconds to two minutes, then check the browser. Multiply that by the eighty or so changes I make in a focused afternoon and I was burning serious time just waiting on rebuilds. Docker Compose Watch removes that tax almost entirely, and it ships in the Compose binary you already have.

The short answer for anyone searching: add a develop.watch block to your compose.yaml, declare which paths should sync, rebuild, or sync+restart, and run docker compose up --watch. Compose Watch went GA in Compose v2.22.0 (bundled with Docker Desktop 4.24), so if you have updated Docker any time since late 2023, you already have it. In this post I will walk through how I use it across a NestJS API and worker setup, where it beats bind mounts, and the gotchas that bit me in real projects.

## Why the Old Local Dev Loop Was Slow

Before Watch, you had two options for live code in containers, and both had sharp edges:

- Full rebuilds on every change. Correct but painfully slow, especially for Node projects where npm install dominates the build even with good layer caching.
- Bind mounts of your source directory. Fast, but they drag your host's node_modules, OS-specific binaries, and editor temp files into the container, causing the classic works-on-my-machine drift you adopted containers to avoid.
- On macOS and Windows, bind mounts also pay a filesystem translation penalty through the VM, which makes file-heavy operations noticeably slower than native Linux.

Compose Watch takes a third path: it observes your host files and performs a one-way copy of changes into the running container. Your image stays the single source of truth for dependencies and system libraries, while your source code flows in at save-time. Container-side changes never leak back to your host, which is exactly the direction of trust you want in development.

## The Three Watch Actions: sync, rebuild, sync+restart

Every watch rule pairs a path with an action. Choosing the right action per path is where the real speedup lives, because the cheapest action that still gives you a correct container wins.

Action

What it does

When I use it

sync

Copies changed files into the running container at the target path, without restarting anything.

Source code in anything with hot reload: NestJS with webpack HMR, Next.js, Vite, nodemon-watched scripts.

rebuild

Rebuilds the image with BuildKit and replaces the running container, like docker compose up --build for that service.

package.json, lockfiles, Dockerfile, anything baked into image layers. Also compiled languages like Go where the binary is the artifact.

sync+restart

Copies the changed files, then restarts the container process without rebuilding the image.

Config files read once at boot: .env files, nginx.conf, ormconfig. The image is fine, the process just needs a fresh start.

## A Real compose.yaml With Watch Rules

Here is a trimmed version of the setup I run for a typical NestJS API plus background worker. Note how each path gets the cheapest viable action, and how ignore keeps test files from triggering pointless syncs:

Then start everything with watch enabled:

> Pro tip: keep using docker compose up --watch (one terminal, logs and watch together) rather than a separate watch session, until your stack grows. With more than three or four services, split logs into another terminal with docker compose logs -f api so sync notifications do not drown your application output.

## Compose Watch vs Bind Mounts: Which One Wins

I still get asked why not just mount the source directory and call it a day. Here is the honest comparison after running both approaches across client projects and my own VPS-deployed apps:

## Compose Watch

One-way host-to-container sync. node_modules stays the container's own, built for Linux inside the image. Editor temp files are filtered automatically, and rapid saves are debounced into batched transfers. Dev environment matches production image behavior closely.

## Bind mounts

Two-way by default, so container writes pollute your host tree. Host node_modules shadows the image's unless you add anonymous volume hacks. Slower I/O through the macOS/Windows VM boundary. Still fine for quick experiments and single-service projects.

My rule of thumb: bind mounts for throwaway prototyping, Compose Watch for anything with a Dockerfile that will eventually ship. The sync action gives you bind-mount-level feedback speed while preserving the image as the contract, and that contract is what makes the jump from laptop to my Docker Swarm production nodes boring, which is the goal.

## Gotchas That Cost Me an Afternoon

Watch only works on services with a build attribute. If a service only has image, Compose has nothing to rebuild or sync against and the watch section is ignored.

The container image needs stat, mkdir, and rmdir binaries for sync to work. Distroless and some ultra-minimal images do not have them, so keep a dev variant of your Dockerfile based on the slim or alpine tags.

The container user must be able to write to the sync target. If your Dockerfile drops to a non-root user (it should in production), make sure that user owns the target directory in your dev stage, or syncs silently fail.

Paths in ignore are relative to the watched path, not the project root. I lost time wondering why my dist exclusion did nothing until I re-read the docs.

> Do not point a sync action at package.json thinking it will install dependencies. Sync only copies files; node_modules in the container will not change. Dependency manifests belong under the rebuild action, full stop.

## Adoption Checklist: Migrating a Project in 15 Minutes

Run docker compose version and confirm you are on v2.22.0 or newer; upgrade Docker Desktop or the Compose plugin if not.

Delete source-code bind mounts from your compose file, keeping only named volumes for databases and caches.

Add a develop.watch block per service: sync for hot-reloadable source, rebuild for dependency manifests and the Dockerfile, sync+restart for boot-time configs.

Add ignore entries for test files, build output, and anything your hot reloader does not need.

Run docker compose up --watch, change a file, and verify you see the sync notification followed by your framework's hot reload log line.

## The Takeaway

Compose Watch is one of those quality-of-life features that quietly pays for itself within the first hour. My measured loop on a mid-size NestJS codebase went from roughly 45 seconds per change with rebuilds to under two seconds with sync plus webpack HMR, and unlike bind mounts I did not trade away environment fidelity to get there.

If your team still rebuilds images on every save, or fights node_modules ghosts from bind mounts, spend the fifteen minutes. The configuration surface is three actions and an ignore list. That is the whole feature, and it is enough.

## Sources and further reading

- Docker Docs: Use Compose Watch
- Docker Blog: Announcing Docker Compose Watch GA Release

## Source links

- https://docs.docker.com/compose/how-tos/file-watch/
- https://www.docker.com/blog/announcing-docker-compose-watch-ga-release/

## FAQ

### What is Docker Compose Watch and what version introduced it?

Docker Compose Watch is a feature that observes your host files and performs a one-way copy of changes into a running container without requiring a full rebuild. It went GA in Compose v2.22.0, bundled with Docker Desktop 4.24. If you have updated Docker any time since late 2023, you already have it.

### What are the three watch actions and when should each one be used?

The three actions are sync, rebuild, and sync+restart. Use sync for source code in frameworks with hot reload such as NestJS with webpack HMR, Next.js, Vite, or nodemon. Use rebuild for dependency manifests like package.json, lockfiles, and the Dockerfile itself. Use sync+restart for config files read once at boot, such as .env files or nginx.conf, where the image is fine but the process needs a fresh start.

### How does Compose Watch compare to bind mounts for local development?

Compose Watch performs a one-way host-to-container sync, keeping node_modules inside the container built for Linux, automatically filtering editor temp files, and debouncing rapid saves into batched transfers. Bind mounts are two-way by default, which can let container writes pollute the host tree and cause node_modules conflicts unless you add anonymous volume workarounds. On macOS and Windows, bind mounts also incur a filesystem translation penalty through the VM that slows file-heavy operations.

### What are the most common gotchas when setting up Compose Watch?

Watch only applies to services that have a build attribute; services with only an image key are silently ignored. Sync also requires the container image to have stat, mkdir, and rmdir binaries, so distroless and ultra-minimal images need a dev variant based on slim or alpine tags. Additionally, do not point a sync action at package.json expecting dependency installation — sync only copies files, so dependency manifests must use the rebuild action instead.

### How much faster is Compose Watch compared to rebuilding on every save?

The author measured their mid-size NestJS codebase loop dropping from roughly 45 seconds per change with full rebuilds to under two seconds using sync combined with webpack HMR. Unlike bind mounts, this speed improvement does not trade away environment fidelity, keeping the development image aligned with how the app behaves in production.

---

- HTML version: https://www.matthewswong.com/en/blog/docker-compose-watch-local-dev/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/docker-compose-watch-local-dev/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
