skip to content

With Docker Compose, when would you use `docker compose watch` with `sync` instead of a bind mount or `rebuild`, and what are its limits?

level: seniorimportance: should knowfreq 33%

answer

  1. one-way copy into a running container
  2. ignore rules a mount cannot give
  3. dependency changes need the image
  4. binaries and a writable target

basics

~20 s

Use watch sync when edited source should reach a running container quickly but selectively: it copies changed files one way into the container and honours ignore rules. Use rebuild for dependency or Dockerfile changes. Container-side writes never come back.

solid answer

~50 s

`docker compose watch`, or `up --watch`, reads each service's `develop.watch` rules (Compose 2.22 and later) and reacts to changes under each `path`. `sync` copies changed files **one way** into the running container at `target` with no restart, which suits an app that hot-reloads; `sync+restart` also restarts the container, for config files; `sync+exec` runs a command after syncing (2.32 and later); `rebuild` rebuilds the image and recreates the container like `up --build <svc>`, for lockfile or Dockerfile changes. Compared with a bind mount, watch can **ignore** paths such as a dependency directory built for the container's platform, and it avoids routing every file read through host file sharing. The limits: files written inside the container never reach the host, the image needs `stat`, `mkdir` and `rmdir` and a user that can write `target`, `rebuild` needs a `build` section, and a `sync` path that is also bind-mounted is skipped with a warning.

code

yaml · 15 lines
yaml
services:
  api:
    build: ./api
    develop:
      watch:
        - path: ./api/src
          action: sync
          target: /app/src
          ignore:
            - generated/
        - path: ./api/config
          action: sync+restart
          target: /app/config
        - path: ./api/dependencies.lock
          action: rebuild

go deeper

for a junior

Recall that docker compose watch reacts to file changes using develop.watch rules, and that sync copies files into the running container while rebuild rebuilds the image.

for a middle

Explain the five actions and which change calls for which: source to sync, config to sync+restart, lockfile or Dockerfile to rebuild. Know that target is required for the sync actions.

for a senior

Show why you would pick watch over a bind mount: ignore rules for platform-specific directories, less file-sharing I/O and one-way copy semantics, plus the image prerequisites that break sync.

for a principal

Weigh watch as a team standard: one documented rule set instead of per-developer bind mounts, and the ongoing cost of keeping those rules in step with the Dockerfile as the project grows.

## The inner-loop problem Rebuilding an image for every one-line edit is slow. Bind-mounting the whole source tree has costs of its own: every file the container reads goes through host file sharing, and directories that must differ between host and container, such as a dependency directory holding native builds for the container's platform or generated output, get shared too. **Compose Watch**, available since Compose 2.22 and assumed here at Compose v5.5.1, is a third option: Compose watches host paths and applies a per-path **action** to the running service. ## How `develop.watch` works 1. You add a `develop.watch` list to a service. Each rule has a `path` (relative to the project directory and watched recursively), an `action`, and, for the sync actions, a `target` path inside the container. 2. `docker compose watch` builds and starts the services, unless you pass `--no-up`, and then watches. `docker compose up --watch` (`-w`) does the same while attached to the service logs; `--watch` cannot be combined with `--detach`. 3. On a change, Compose matches the file against the rules, skipping anything that matches the rule's `ignore` patterns, the build context's `.dockerignore`, `.git` directories and common editor temporary files. 4. It applies the action. After a `rebuild`, it removes the superseded dangling image unless you run `docker compose watch --prune=false`. ## The five actions | `action` | What Compose does | Use it for | |---|---|---| | `sync` | copies changed files to `target` in the running container | source an app hot-reloads | | `sync+restart` | syncs, then restarts the container | config files read at start-up | | `sync+exec` | syncs, then runs the rule's `exec.command` in the container | a reload or cache-clear command | | `restart` | restarts the container without copying anything | files already visible through a bind mount that the app reads only at start-up | | `rebuild` | rebuilds the image and recreates the container, like `docker compose up --build <svc>` | lockfiles, the Dockerfile, compiled code | `restart` and `sync+exec` arrived in Compose 2.32, so releases before it offer neither. ## Watch versus a bind mount - **Granularity:** rules can ignore paths inside the watched tree, while a bind mount shares everything under it. - **Performance:** only changed files are copied, instead of every read crossing host file sharing. - **Platform separation:** a dependency directory built for the container's platform stays in the image and is never overwritten from the host. - **Direction:** a bind mount is two-way, while `sync` is one-way, host to container. The Compose documentation presents watch as a companion to bind mounts, not a replacement. When a `sync` or `sync+restart` path is also declared as a bind mount on the same service, Compose warns and does not monitor that path. ## Limits and traps - **One way only.** Files the app writes inside the container, such as generated assets, stay there. - **Image prerequisites.** Sync copies an archive into the container and runs `rm -rf` there for deleted files; the documentation asks for `stat`, `mkdir` and `rmdir` in the image and a container user that can write to `target`. - **Validation.** `target` is required for `sync`, `sync+restart` and `sync+exec`, `sync+exec` needs an `exec.command`, and `rebuild` needs a `build` section and refuses `--no-build`. - **Rebuild is still a rebuild.** A `rebuild` rule is only as fast as the image build; watch shortens the loop for source, not for dependency changes. ## Choosing a mechanism per path 1. **Source that the app reloads on change:** `sync`. 2. **Configuration read only at start-up:** `sync+restart`, or `sync+exec` when the app has its own reload command. 3. **Anything installed or compiled during the image build,** such as dependencies from a lockfile: `rebuild`. 4. **Data that must flow both ways,** such as files the container generates for you to inspect: a bind mount. Rules are only needed on the services you edit, and `ignore` patterns are resolved relative to the rule's own `path`, not the project directory. ## A rule set for the API stack ```yaml services: api: build: ./api develop: watch: - path: ./api/src action: sync target: /app/src ignore: - generated/ - path: ./api/config action: sync+restart target: /app/config - path: ./api/dependencies.lock action: rebuild ``` Source edits appear in seconds, a config edit restarts the container, and only a dependency change pays for an image build. The `db` and `redis` services need no rules at all.

  • What happens to files the API writes inside its container while `sync` is watching `./api/src`?
    They stay in the container. Sync is one-way: Compose copies files changed on the host into the container and deletes there the files you deleted on the host, but it never copies container-side writes back. If the host needs those files, use a bind mount for that directory or copy them out with `docker compose cp`.
  • Why does `docker compose up -d --watch` fail?
    Watching is a foreground process that listens for file-system events, so Compose rejects `--watch` together with `--detach`. Run `docker compose up --watch` attached, or start the stack with `up -d` and run `docker compose watch --no-up` in another terminal.
  • A teammate declares `./api/src` both as a bind mount and as a `sync` path. What happens?
    Compose warns that the path is also declared by a bind mount and does not monitor it for that service. The bind mount already exposes the files, so the sync rule adds nothing; choose one mechanism per path.

saying these in an interview costs you the question

  • watch sync keeps host and container in two-way sync, like a bind mount.
  • A sync rule quietly rebuilds the image in the background.
  • A rebuild rule works on a service that has only image: and no build section.
  • docker compose up -d --watch runs the watcher in the background.
  • Once watch is on, dependency changes are picked up by sync alone.