docker/compose · error
watch rules MUST define a path
Error message
watch rules MUST define a path
What it means
Before watching, Compose normalizes each develop.watch rule's path: relative paths are joined with the project base dir, symlinks evaluated, and the result cleaned. If the resulting path is the empty string — i.e. the rule effectively defines no path — the config is rejected because the file watcher has nothing to observe.
Source
Thrown at pkg/compose/watch.go:421
if err != nil {
return nil, err
}
baseDir, err := filepath.EvalSymlinks(project.WorkingDir)
if err != nil {
return nil, fmt.Errorf("resolving symlink for %q: %w", project.WorkingDir, err)
}
for i, trigger := range config.Watch {
if !filepath.IsAbs(trigger.Path) {
trigger.Path = filepath.Join(baseDir, trigger.Path)
}
if p, err := filepath.EvalSymlinks(trigger.Path); err == nil {
// this might fail because the path doesn't exist, etc.
trigger.Path = p
}
trigger.Path = filepath.Clean(trigger.Path)
if trigger.Path == "" {
return nil, errors.New("watch rules MUST define a path")
}
if trigger.Action == types.WatchActionRebuild && service.Build == nil {
return nil, fmt.Errorf("service %s doesn't have a build section, can't apply %s on watch", types.WatchActionRebuild, service.Name)
}
if trigger.Action == types.WatchActionSyncExec && len(trigger.Exec.Command) == 0 {
return nil, fmt.Errorf("can't watch with action %q on service %s without a command", types.WatchActionSyncExec, service.Name)
}
config.Watch[i] = trigger
}
return &config, nil
}
func checkIfPathAlreadyBindMounted(watchPath string, volumes []types.ServiceVolumeConfig) bool {
for _, volume := range volumes {
if volume.Bind != nil {
relPath, err := filepath.Rel(volume.Source, watchPath)View on GitHub (pinned to ddc4b044b6)
Solutions
- Add an explicit `path:` to the watch rule (relative to the service build context or absolute)
- If the path comes from interpolation, default it: `path: ${WATCH_PATH:-./src}`
- Remove rules you do not need instead of leaving them with empty fields
Example fix
# before
services:
web:
develop:
watch:
- action: sync
path: ""
target: /app
# after
services:
web:
develop:
watch:
- action: sync
path: ./src
target: /app Defensive patterns
Strategy: validation
Validate before calling
python3 - <<'EOF'
import yaml,sys
cfg=yaml.safe_load(open('compose.yaml'))
for n,s in (cfg.get('services') or {}).items():
for r in ((s.get('develop') or {}).get('watch') or []):
if not r.get('path'): sys.exit(f"service {n}: watch rule missing path")
print('ok')
EOF Prevention
- Default interpolated paths: path: ${WATCH_PATH:-./src}
- Validate with `docker compose config` after template-rendering compose files
When it happens
Trigger: A develop.watch rule whose path resolves to empty: typically `path:` omitted entirely (empty struct field) when baseDir is also empty, or a template-generated compose file emitting `path: ""`.
Common situations: YAML anchors or env interpolation producing an empty path (${WATCH_PATH} unset); hand-writing a watch rule and forgetting the path key; machine-generated compose files with sparse watch configs.
Related errors
- healthcheck.start_interval requires healthcheck.start_period
- 'compose' is not a valid provider type
- your Compose stack cannot be published as it only contains a
- service %q build configuration does not support platform: %s
- --wait cannot be combined with --abort-on-container-exit, --
AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15).
Data as JSON: /api/errors/75ba533e9c8bc310.
Report an issue: GitHub.