{"record":{"id":"ca100d44cecf44e6","repo":"docker/cli","slug":"destination-s-s-must-be-a-directory","errorCode":null,"errorMessage":"destination \"%s:%s\" must be a directory","messagePattern":"destination \"(.+?):(.+?)\" must be a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/command/container/cp.go","lineNumber":414,"sourceCode":"\t\t// not exist, but the parent directory does, the extraction will still\n\t\t// succeed.\n\t\t_ = err // Intentionally ignore stat errors (see above)\n\t}\n\n\tvar (\n\t\tcontent         io.ReadCloser\n\t\tresolvedDstPath string\n\t\tcopiedSize      int64\n\t\tcontentSize     int64\n\t\tsizeErr         error\n\t)\n\n\tif srcPath == \"-\" {\n\t\tcontent = os.Stdin\n\t\tresolvedDstPath = dstInfo.Path\n\t\tsizeErr = errors.New(\"content size not available for stdin\")\n\t\tif !dstInfo.IsDir {\n\t\t\treturn fmt.Errorf(`destination \"%s:%s\" must be a directory`, copyConfig.container, dstPath)\n\t\t}\n\t} else {\n\t\t// Prepare source copy info.\n\t\tsrcInfo, err := archive.CopyInfoSourcePath(srcPath, copyConfig.followLink)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tcontentSize, sizeErr = localContentSize(srcInfo.Path)\n\n\t\tsrcArchive, err := archive.TarResource(srcInfo)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer srcArchive.Close()\n\n\t\t// With the stat info about the local source as well as the\n\t\t// destination, we have enough information to know whether we need to","sourceCodeStart":396,"sourceCodeEnd":432,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/command/container/cp.go#L396-L432","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"# before\ntar c file.txt | docker cp - mycontainer:/tmp/file.txt   # not a directory\n# after\ndocker exec mycontainer mkdir -p /tmp/incoming\ntar c file.txt | docker cp - mycontainer:/tmp/incoming/","handlingStrategy":"validation","validationCode":"// For `docker cp -`, ensure the destination is an existing directory.\nfunc ensureCpStdinDestIsDir(ctx context.Context, c client.APIClient, container, path string) error {\n    st, err := c.ContainerStatPath(ctx, container, client.ContainerStatPathOptions{Path: path})\n    if err != nil { return fmt.Errorf(\"destination must exist as a directory for stdin copy: %w\", err) }\n    if !st.Stat.Mode.IsDir() { return errors.New(\"destination must be a directory for stdin copy\") }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pre-create the destination directory with docker exec <c> mkdir -p <path>.","Prefer explicit trailing slash on directory destinations to signal intent."],"tags":["container","copy","stdin","archive","validation"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}