docker/cli · error

destination " : " must be a directory

Error message

destination "%s:%s" must be a directory

What it means

Returned during `docker cp -` (streaming content from stdin into a container) when the resolved destination is not a directory (cli/command/container/cp.go:413-415). When srcPath is '-', the stream is treated as a tar archive that must be extracted into a directory, so a non-directory destination is rejected before any data is sent.

Solutions

  1. Ensure the destination path is an existing directory inside the container: docker exec <c> mkdir -p <path>.
  2. Point at a known directory like /tmp/ or /data/.
  3. If copying a single file rather than an archive, pipe into a file via docker exec instead: cat file | docker exec -i <c> tee /path/file.

Example fix

# before
tar c file.txt | docker cp - mycontainer:/tmp/file.txt   # not a directory
# after
docker exec mycontainer mkdir -p /tmp/incoming
tar c file.txt | docker cp - mycontainer:/tmp/incoming/
Defensive patterns

Strategy: validation

Validate before calling

// For `docker cp -`, ensure the destination is an existing directory.
func ensureCpStdinDestIsDir(ctx context.Context, c client.APIClient, container, path string) error {
    st, err := c.ContainerStatPath(ctx, container, client.ContainerStatPathOptions{Path: path})
    if err != nil { return fmt.Errorf("destination must exist as a directory for stdin copy: %w", err) }
    if !st.Stat.Mode.IsDir() { return errors.New("destination must be a directory for stdin copy") }
    return nil
}

Prevention

When it happens

Trigger: Running `docker cp - <container>:<path>` where <path> does not exist as a directory or resolves to a regular file. Because stdin content is an archive, the destination must be a directory to extract into.

Common situations: Forgetting to create the target directory, pointing at a file path, or assuming the destination is a directory when it is absent (cp treats a missing destination leniently only in some branches; the stdin branch requires IsDir).

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/ca100d44cecf44e6. Report an issue: GitHub.

Appendix: source

Thrown at cli/command/container/cp.go:414

		// not exist, but the parent directory does, the extraction will still
		// succeed.
		_ = err // Intentionally ignore stat errors (see above)
	}

	var (
		content         io.ReadCloser
		resolvedDstPath string
		copiedSize      int64
		contentSize     int64
		sizeErr         error
	)

	if srcPath == "-" {
		content = os.Stdin
		resolvedDstPath = dstInfo.Path
		sizeErr = errors.New("content size not available for stdin")
		if !dstInfo.IsDir {
			return fmt.Errorf(`destination "%s:%s" must be a directory`, copyConfig.container, dstPath)
		}
	} else {
		// Prepare source copy info.
		srcInfo, err := archive.CopyInfoSourcePath(srcPath, copyConfig.followLink)
		if err != nil {
			return err
		}

		contentSize, sizeErr = localContentSize(srcInfo.Path)

		srcArchive, err := archive.TarResource(srcInfo)
		if err != nil {
			return err
		}
		defer srcArchive.Close()

		// With the stat info about the local source as well as the
		// destination, we have enough information to know whether we need to

View on GitHub (pinned to 4f84911bfe)