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
- Ensure the destination path is an existing directory inside the container: docker exec <c> mkdir -p <path>.
- Point at a known directory like /tmp/ or /data/.
- 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
- Pre-create the destination directory with docker exec <c> mkdir -p <path>.
- Prefer explicit trailing slash on directory destinations to signal intent.
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
- destination " : " must be a directory or a regular file
- error reading from STDIN: data is empty
- cannot attach to a stopped container, start it first
- cannot attach to a paused container, unpause it first
- cannot attach to a restarting container, wait until it is…
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 toView on GitHub (pinned to 4f84911bfe)