Live reload without bind mount hacks
Anyone running local development environments with Docker Compose knows the problem: a bind mount mirrors everything, but node_modules, vendor directories and build artifacts cause performance issues and conflicts between host and container. Docker Compose Watch solves this precisely with three actions, sync, rebuild and sync and restart, finally making live reload predictable.
Table of Contents
- 1. Why Docker Compose Watch exists
- 2. Basic configuration: understanding the watch directive
- 3. The sync action: mirroring files without a restart
- 4. The rebuild action: rebuilding images on dependency changes
- 5. The sync and restart action: applying config files cleanly
- 6. Practical example: Node.js frontend with live reload
- 7. Practical example: PHP and Magento with compose watch
- 8. Limits, ignore rules and debugging
- 9. compose watch compared to bind mounts and external tools
- 10. Summary
- 11. FAQ
1. Why Docker Compose Watch exists
Before Docker Compose Watch, there was essentially only one way to get live reload in containers: put a bind mount of the entire project directory into the container and hope the rest works out. The problem is that a bind mount makes no distinction. It mirrors source code just as much as node_modules, vendor directories, build caches and temporary files, and this causes noticeable performance drops especially on macOS and Windows, because every filesystem operation has to pass through the virtualized layer.
Docker Compose Watch solves this problem by making synchronization explicit. Instead of a blanket mount, you define in the compose.yaml exactly which paths should be observed and which action should be triggered on a change. The result is a live reload setup that only synchronizes the files that actually matter, while node_modules or vendor stay entirely inside the container and are managed there at native speed. For teams working with Docker Compose every day, this is a noticeable leap in development experience.
Another advantage of compose watch over classic bind mounts: the configuration lives directly in the compose.yaml and is therefore part of the project, versioned and identical for every developer. Nobody has to manually adjust volumes anymore or configure external tools like nodemon or browser-sync separately just to detect changes. Docker Compose itself takes over the file watching and decides based on the defined rules whether to sync, rebuild or restart.
2. Basic configuration: understanding the watch directive
The watch directive is defined per service under the develop.watch key. Each entry in this list describes a path, an action and optional filters. The path specifies which directory or file is observed, relative to the compose.yaml. The action determines what happens on a change, the options are sync, rebuild and sync and restart. Optionally, target specifies where inside the container to sync to, if that differs from the source path.
Compose watch is started with docker compose watch, either alongside an already running docker compose up, or combined into a single command with docker compose up --watch. Important for daily development use: compose watch exclusively observes changes on the host filesystem, it does not replace healthchecks or orchestration, but complements an already running compose stack with file watching functionality.
# compose.yaml — minimal watch configuration
services:
web:
build: .
ports:
- "3000:3000"
develop:
watch:
# Sync source files without rebuilding the image
- action: sync
path: ./src
target: /app/src
# Rebuild the image when dependency files change
- action: rebuild
path: ./package.json
# Sync config and restart the process (no image rebuild)
- action: sync+restart
path: ./config
target: /app/config
These three action types cover almost all the typical change scenarios in a modern application. Source code changes constantly and only needs quick synchronization. Dependencies change less often but require a full rebuild, because the image layers themselves change. Config files change occasionally and need a process restart, but no new image. Docker Compose Watch maps exactly these three cases, without a developer having to write their own file watching scripts.
3. The sync action: mirroring files without a restart
The sync action is the most common and lightest option of Docker Compose Watch. As soon as a file changes in the observed path, Compose copies the change directly into the running container, without rebuilding the image and without restarting the container. For development setups with hot module replacement, for example Vite, the Webpack dev server or Node with nodemon inside the container, this is completely sufficient, because the process inside the container itself reacts to the filesystem change.
An important distinction with sync is between path and target. The path always refers to the host, relative to the compose.yaml, while target specifies the absolute target path inside the container. This separation allows different directory structures between host and container, which is particularly useful in monorepos or when the working directory in the image differs from the local structure. Without target, compose watch uses the same relative path as defined in the container working directory.
A common mistake when using sync is that developers try to sync node_modules or vendor through it. That technically works, but is slow and unnecessary, because these directories should live in the image anyway and only need to change when package.json or composer.json changes, which is exactly what the rebuild action is for. The rule of thumb is: sync for source code, rebuild for dependencies.
4. The rebuild action: rebuilding images on dependency changes
When a file changes that affects the build process, for example package.json, package-lock.json, composer.json or the Dockerfile itself, a plain sync is not enough. This is where the rebuild action comes in. Compose watch detects the change, rebuilds the image with docker compose build and then automatically restarts the affected service, without any manual intervention needed.
The advantage over a manual docker compose up --build lies in automating the entire cycle. As soon as a developer installs a new dependency and saves package.json, the rebuild runs automatically in the background, while compose watch keeps watching for further changes. This significantly reduces context switching, because developers no longer have to manually switch between editor and terminal to trigger a rebuild.
# compose.yaml — rebuild triggers for dependency changes
services:
api:
build:
context: .
dockerfile: Dockerfile
develop:
watch:
- action: sync
path: ./src
target: /app/src
# Any change to these files triggers a full image rebuild
- action: rebuild
path: ./package.json
- action: rebuild
path: ./package-lock.json
- action: rebuild
path: ./Dockerfile
An important note: rebuild cycles should be used deliberately sparingly, because every rebuild costs time, even with BuildKit caching. Only files that actually trigger layer changes in the image belong in a rebuild rule. Source code that changes constantly during development should always run through sync, never through rebuild, otherwise Docker Compose Watch loses exactly the speed advantage it was designed for.
5. The sync and restart action: applying config files cleanly
Between sync and rebuild lies a third case that comes up very often in practice: files that do not require an image rebuild, but a restart of the process inside the container for the change to take effect. Typical examples are environment configurations, PHP ini files, Nginx configurations or server start scripts, which are read at process start but not reloaded on every filesystem change.
For exactly this case there is the sync and restart action. Compose watch first copies the file into the container as with sync and then restarts the container, so the process starts up with the updated configuration. The key difference from rebuild is that no new image is built, only the existing container is restarted, which is significantly faster than a full build cycle.
# compose.yaml — sync+restart for config that needs a process reload
services:
php:
build: .
develop:
watch:
- action: sync
path: ./src
target: /var/www/html/src
# Config changes need a restart but not a rebuild
- action: sync+restart
path: ./docker/php.ini
target: /usr/local/etc/php/conf.d/custom.ini
- action: sync+restart
path: ./docker/nginx.conf
target: /etc/nginx/conf.d/default.conf
In practice it helps to consciously think of all three actions in terms of their cost order. sync is the cheapest, sync and restart costs a process restart, rebuild costs a full image build. Whoever keeps this order in mind quickly finds the right category for every file in the project and avoids unnecessarily long waiting times during development with Docker Compose Watch.
6. Practical example: Node.js frontend with live reload
A typical Node.js frontend with Vite or the Webpack dev server benefits especially strongly from Docker Compose Watch, because the dev server itself already supports hot module replacement as soon as files inside the container are updated. The job of compose watch here is limited to reliably and quickly getting changes from the host editor into the container, without node_modules ever having to be synced over the network or the virtualized filesystem layer.
It is important to leave node_modules inside the container and not overwrite it through a classic bind mount from the host. This is achieved by running npm install inside the Dockerfile and not defining any volume for node_modules in the compose.yaml, only configuring sync for the src folder. If package.json changes, the rebuild rule automatically kicks in and the dependencies are reinstalled inside the image.
# Start the stack and watch mode together in one command
docker compose up --watch
# Or run watch mode against an already running stack
docker compose up -d
docker compose watch
# Check which services are configured for watch mode
docker compose config --services
7. Practical example: PHP and Magento with compose watch
For PHP projects, especially Magento shops with many module directories, Docker Compose Watch is also very effective, but needs to be configured more precisely than for a single Node process. PHP-FPM rereads files on every request as long as OPcache runs in development mode, so a plain sync action for app/code or src is already enough to make code changes visible immediately.
For vendor dependencies managed through composer.json, the rebuild rule applies again, because composer install changes layers inside the image. Config files like php.ini overrides or Xdebug settings belong in the sync and restart category, because PHP-FPM only reads these values at process start. This creates a setup where developers can work on Magento modules while compose watch automatically decides in the background whether to sync, restart or rebuild.
# compose.yaml — PHP/Magento watch setup
services:
php-fpm:
build: ./docker/php
develop:
watch:
- action: sync
path: ./src/app/code
target: /var/www/html/app/code
- action: rebuild
path: ./composer.json
- action: sync+restart
path: ./docker/php/conf.d
target: /usr/local/etc/php/conf.d
8. Limits, ignore rules and debugging
Docker Compose Watch is not a universal file watching system and has some limits worth knowing before relying on it entirely. Very large directories with thousands of files can affect detection speed, especially when no ignore rules are defined. Through ignore entries inside a watch block, subdirectories like node_modules, vendor or .git can be explicitly excluded, even if they technically lie within an observed path.
For debugging compose watch itself, the --verbose parameter helps, logging every detected filesystem change and the resulting action. This is especially useful when an expected sync is not triggered, because for example a symlink or an editor with atomic saves through a temporary file bypasses the change detection. In such cases it often helps to touch the affected file directly using the touch command, to check whether the watch rule works at all.
Another limitation: compose watch does not replace production deployment mechanisms. It is intended exclusively for local development and should never be active in production compose.yaml files, because automatically restarting and rebuilding containers in a production environment can have uncontrollable side effects.
9. compose watch compared to bind mounts and external tools
Before Docker Compose Watch, teams usually solved the live reload problem with a combination of bind mounts and external tools like nodemon, entr or their own shell scripts that talked to inotify directly. These solutions work, but bring additional complexity, because they need to be configured and maintained outside the compose.yaml and often do not behave the same way across platforms.
| Approach | Configuration location | node_modules problem | Cross-platform consistency |
|---|---|---|---|
| Classic bind mount | compose.yaml (volumes) | Everything is mirrored, slow | Much slower on macOS/Windows |
| nodemon inside the container | separate config file | Has to be excluded separately | Depends on the underlying bind mount |
| Custom inotify script | external shell script | Manual maintenance | Native only on Linux, fragile elsewhere |
| Docker Compose Watch | compose.yaml (develop.watch) | Stays in the image, no sync needed | Consistent across the Compose engine |
The decisive difference is that Docker Compose Watch integrates the synchronization logic directly into the Compose engine, instead of solving it through bind mounts and extra processes inside the container. This makes the configuration part of the versioned compose.yaml, every developer on the team automatically gets the same live reload experience, and there is no need to manually maintain watcher tools and their ignore lists inside the image.
Mironsoft
Docker development environments and container infrastructure
Live reload that works the same for the whole team?
We set up Docker Compose Watch for your development environment, remove fragile bind mounts and make sure Node, PHP and Magento stacks reload quickly and reliably.
Setup audit
Reviewing existing compose.yaml files for bind mount problems and missing ignore rules
Watch configuration
Setting up sync, rebuild and sync and restart to match your project
Magento Docker
Live reload for PHP-FPM, Nginx and Node build processes in one stack
10. Summary
Docker Compose Watch solves a problem that has accompanied local development environments with bind mounts for years: the uncontrolled mirroring of entire directories including node_modules and vendor. With the three actions sync, rebuild and sync and restart, it is possible to define precisely for every file in the project how a change is handled, without needing external watcher tools. Source code is synced quickly, dependency changes automatically trigger a rebuild, and config files lead to a targeted restart of the affected container.
The biggest advantage lies in team consistency: the watch configuration is versioned inside the compose.yaml, every developer gets the same live reload experience, regardless of operating system. Anyone who consistently keeps node_modules and vendor inside the image and only syncs actual source code via sync gains a noticeable speed advantage over classic bind mount setups, especially on macOS and Windows.
Docker Compose Watch — The Essentials at a Glance
sync
Copies file changes directly into the running container, ideal for source code with hot module replacement.
rebuild
Automatically rebuilds the image on changes to package.json or Dockerfile and restarts the service.
sync+restart
Syncs config files and restarts the process, without building a new image.
Ignore rules
Exclude node_modules, vendor and .git via ignore to keep detection speed high.