Skip to content

[BUG] watch says: Error response from daemon: cannot overwrite non-directory "xxx" with directory "yyy" #13795

Closed

Description

@lumbric

Description

When I do a docker compose up --watch I get the error:

copying files to dff6....: Error response from daemon: cannot overwrite non-directory "/some/path/in/image" with directory "/"

In this case /some/path/in/image is a relative symlink.

The error occurs only with ---watch which probably means that this is an issue with the initial sync of compose watch.

Steps To Reproduce

Unfortunately I have not managed to reproduce the issue, but I have seen it multiple times.

  • It always occurs after switching to a Git commit where folder structure is entirely different.
  • The first time I was able to fix it by running docker system prune. The second time by docker compose build --no-cache.
  • The error occurs only with --watch. I can start containers successfully without watch.

Compose Version

$ docker compose version
Docker Compose version v5.1.3

Docker Environment

$ docker info
Client: Docker Engine - Community
 Version:    29.5.0
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc.)
    Version:  v0.34.0
    Path:     /usr/libexec/docker/cli-plugins/docker-buildx
  compose: Docker Compose (Docker Inc.)
    Version:  v5.1.3
    Path:     /usr/libexec/docker/cli-plugins/docker-compose

Server:
 Containers: 7
  Running: 3
  Paused: 0
  Stopped: 4
 Images: 10
 Server Version: 29.5.0
 Storage Driver: overlayfs
  driver-type: io.containerd.snapshotter.v1
 Logging Driver: json-file
 Cgroup Driver: systemd
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
 CDI spec directories:
  /etc/cdi
  /var/run/cdi
 Swarm: inactive
 Runtimes: io.containerd.runc.v2 runc
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 77c84241c7cbdd9b4eca2591793e3d4f4317c590
 runc version: v1.3.5-0-g488fc13e
 init version: de40ad0
 Security Options:
  apparmor
  seccomp
   Profile: builtin
  cgroupns
 Kernel Version: 6.1.0-44-amd64
 Operating System: Neptune 8.2
 OSType: linux
 Architecture: x86_64
 CPUs: 12
 Total Memory: 33.64GiB
 Name: longy0201
 ID: 2b992073-edb2-48f1-b2c1-81e969c69a58
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 Experimental: false
 Insecure Registries:
  ::1/128
  127.0.0.0/8
 Live Restore Enabled: false
 Firewall Backend: iptables
  EnableUserlandProxy: true
  UserlandProxyPath: /usr/bin/docker-proxy

Anything else?

No response

Activity

  1. adityasharmawork commented on May 31, 2026

    @adityasharmawork

    Hey Maintainers!

    I dug into where this error comes from. It's the watch sync step, not the build, which is why a plain up works and only --watch hits it.

    When watch syncs changed paths, it builds a tar and unpacks it into the running container through the daemon's CopyToContainer at / (tarDockerClient.Untar in pkg/compose/watch.go). That call doesn't set AllowOverwriteDirWithFile, so the unpack runs with noOverwriteDirNonDir on. If the tar carries a directory entry for a path that already exists in the image as a non-directory (your relative symlink), the daemon refuses and returns exactly:

    cannot overwrite non-directory "<path>" with directory "/"

    So the condition is a type mismatch at one path: a directory on the host side, a symlink (or file) in the image. docker system prune / build --no-cache clear it only because the rebuilt image no longer has the symlink there.

    It's hard to reproduce on demand because the initial sync only ever copies files, never whole directories (initialSyncFiles skips directories on purpose). The directory entry that trips this comes from the regular event-driven sync, e.g. when a directory appears or changes under the watched path, which lines up with it happening "after switching to a commit where the folder structure is entirely different".

    Before I open a PR, I'd like to confirm the intended behavior:

    1. Have the watch sync pass AllowOverwriteDirWithFile: true, so it replaces the conflicting symlink/file with the host directory (host as the source of truth, which is what watch is for).
    2. Keep failing, but replace the raw daemon error with a clear message that names the host-directory vs image-symlink conflict.

    I'm leaning towards 1, but that would make watch behave differently from docker compose cp, which intentionally keeps the no-overwrite default (AllowOverwriteDirWithFile: false), so I didn't want to change the behavior without checking.

    Happy to send the PR with a regression test for whichever you'd prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions