Docker Compose Watch: When to Use sync, rebuild, and sync+restart
Docker Compose Watch is most useful when each file change gets the smallest necessary container action. Source edits usually need a sync; a changed dependency manifest needs a rebuild; a configuration change that the process reads only at startup needs sync+restart.
The common mistake is to make every rule rebuild the image. That works, but it turns a one-second edit into a slow feedback loop and can hide whether the application actually supports hot reload. Start with the action that matches the changed file, then add a stronger action only at the boundary where it is required.
The three actions solve different problems
| Action | What Compose does | Good fit |
|---|---|---|
sync | Copies changed files into the running container. | Source files handled by a dev server. |
rebuild | Builds a new image and replaces the container. | package.json, lockfiles, or a Dockerfile change. |
sync+restart | Copies the file, then restarts the service. | Configuration read only during process startup. |
The Compose Watch documentation also documents restart and sync+exec for newer Compose versions. This article focuses on the three actions that cover most application development loops.
A practical compose.yaml
The following example keeps application source changes fast, rebuilds when dependencies change, and restarts after a configuration change. Adjust the paths and the container working directory to match your image:
services: web: build: context: . command: npm run dev -- --host 0.0.0.0 ports: - '4321:4321' develop: watch: - action: sync path: ./src target: /app/src ignore: - node_modules/ - action: rebuild path: ./package.json - action: rebuild path: ./package-lock.json - action: sync+restart path: ./config target: /app/configThe sync rule assumes the image already contains the dependencies and that the dev server watches /app/src. If the application writes generated files into that tree, ignore those files or choose a narrower source path so the container does not continually copy its own output back and forth.
For pnpm or Yarn projects, replace package-lock.json with the lockfile that controls your install. A lockfile change belongs to rebuild because the container needs a fresh dependency installation; copying it into a running container does not install anything.
Understand path, target, and ignore
path is the host-side path being watched. target is the destination inside the container and is required for sync actions. In the example, a change to src/components/Card.tsx is copied to /app/src/components/Card.tsx.
The subtle part is ignore: its patterns are relative to the watch rule’s path, not automatically to the project root. A rule watching ./src should express an ignored node_modules directory relative to src. The Compose reference also notes that .dockerignore patterns are implicitly loaded, so check both places when a file does not sync.
Keep the watched source directory smaller than the repository when possible. Do not watch .git, build output, caches, secret files, or a host-side node_modules directory unless you have a specific reason. Narrow rules make event loops and accidental secret copies less likely.
Check the version and container permissions
Compose Watch requires a sufficiently recent Compose implementation. The develop specification is available from Compose 2.22.0, while individual actions have their own version requirements. Confirm the version on the machine running the workflow:
docker compose versiondocker compose config --quietdocker compose up --watchThe container user must be able to create, remove, and modify files at the sync target. If the watcher reports permission errors, inspect the target directory and its ownership from inside the service:
docker compose exec web sh -lc 'id && ls -ld /app /app/src'Fix the image or development-user permissions deliberately. Making the entire container filesystem writable as a shortcut can hide a real ownership problem and can make the development environment differ from production.
Use docker compose watch when the services are already running and you want to attach the watcher separately. Use docker compose up --watch when the normal startup and the watch loop should be one command.
Know when sync is not enough
Use rebuild when the changed file affects the image or installed dependencies:
Dockerfileor build argumentspackage.json, a lockfile, or Python dependency files- OS packages or generated code baked into the image
Use sync+restart when copying the file is correct but the process reads it only once:
- reverse-proxy configuration
- a server configuration file
- a development-only feature flag loaded at startup
Do not place secrets in a watched directory just because the rule is convenient. Keep secrets in the environment or the secret mechanism appropriate to the deployment. Watch is a development feedback tool, not a production configuration rollout system.
If you are containerizing an Astro site behind Caddy, the Astro and Caddy Docker guide covers the production image boundary; Compose Watch belongs in the local development layer around that boundary.
FAQ
Does Compose Watch replace bind mounts?
Not always. Watch provides explicit file actions and can be easier to reason about than a broad bind mount, but a bind mount may still be appropriate for a particular local workflow. Avoid combining overlapping mechanisms until you know which one owns each path.
Why does a dependency change not take effect after sync?
sync copies the manifest but does not install its dependencies. Give the manifest and lockfile rebuild rules so Compose builds an image with the new dependency tree.
Why does a config change still use the old value?
The process may read that file only at startup. Use sync+restart, or use a narrower restart rule when the file is already present in the container and only a restart is needed.
References:
Report a typo or broken link, or suggest a related topic.