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

  1. Add an explicit `path:` to the watch rule (relative to the service build context or absolute)
  2. If the path comes from interpolation, default it: `path: ${WATCH_PATH:-./src}`
  3. 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

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


AI-assisted analysis of docker/compose@ddc4b044b6 (2026-08-15). Data as JSON: /api/errors/75ba533e9c8bc310. Report an issue: GitHub.