{"record":{"id":"d50b20b8f7ec7b86","repo":"docker/cli","slug":"failed-to-save-image-w","errorCode":null,"errorMessage":"failed to save image: %w","messagePattern":"failed to save image: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/command/image/save.go","lineNumber":79,"sourceCode":"\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid platform: %w\", err)\n\t\t}\n\t\tplatformList = append(platformList, pp)\n\t}\n\tif len(platformList) > 0 {\n\t\toptions = append(options, client.ImageSaveWithPlatforms(platformList...))\n\t}\n\n\tvar output io.Writer\n\tif opts.output == \"\" {\n\t\tif dockerCLI.Out().IsTerminal() {\n\t\t\treturn errors.New(\"cowardly refusing to save to a terminal. Use the -o flag or redirect\")\n\t\t}\n\t\toutput = dockerCLI.Out()\n\t} else {\n\t\twriter, err := atomicwriter.New(opts.output, 0o600)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to save image: %w\", err)\n\t\t}\n\t\tdefer writer.Close()\n\t\toutput = writer\n\t}\n\n\tresponseBody, err := dockerCLI.Client().ImageSave(ctx, opts.images, options...)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer responseBody.Close()\n\n\t_, err = io.Copy(output, responseBody)\n\treturn err\n}\n","sourceCodeStart":61,"sourceCodeEnd":94,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/command/image/save.go#L61-L94","documentation":"Returned by `docker image save` / `docker save` when the atomic output file cannot be created via `atomicwriter.New(opts.output, 0o600)` (save.go:77-79). atomicwriter writes to a temp file then renames, so this fails when the path is invalid, the parent directory is missing, or the process lacks write permission. The %w wraps the underlying OS error (e.g. EACCES, ENOENT, EISDIR).","triggerScenarios":"Running `docker save -o /nonexistent/dir/img.tar img` (parent dir missing), `-o /some/dir` where the path is an existing directory, or `-o /root/file.tar` as a non-root user. Also triggered on read-only filesystems.","commonSituations":"Typos in the -o path, pointing -o at a directory instead of a file, running in a container/CI where the output mount is read-only, or a path with no permission.","solutions":["Verify the parent directory of -o exists and is writable: `mkdir -p $(dirname OUT) && touch OUT`.","Ensure -o points to a file path, not an existing directory.","Drop -o and pipe to a writable location: `docker save img > out.tar`.","Check disk space and that the filesystem is mounted read-write."],"exampleFix":"# before\ndocker save -o /tmp/missing-dir/img.tar myimage\n# after\nmkdir -p /tmp/missing-dir && docker save -o /tmp/missing-dir/img.tar myimage","handlingStrategy":"validation","validationCode":"// validate the -o output path before calling ImageSave\ninfo, err := os.Stat(filepath.Dir(outputPath))\nif err != nil || !info.IsDir() {\n    return fmt.Errorf(\"output directory for %q is not accessible: %w\", outputPath, err)\n}\nif f, err := os.Create(outputPath); err != nil {\n    return fmt.Errorf(\"output path %q not writable: %w\", outputPath, err)\n} else {\n    f.Close()\n    os.Remove(outputPath)\n}","typeGuard":null,"tryCatchPattern":"if err := runSave(ctx, cli, opts); err != nil {\n    if strings.Contains(err.Error(), \"failed to save image\") {\n        // surface an actionable hint about the -o path\n        log.Fatalf(\"save failed — check that -o path is a writable file in an existing directory: %v\", err)\n    }\n    log.Fatal(err)\n}","preventionTips":["Always mkdir -p the parent of -o before running docker save.","Never point -o at an existing directory.","In CI, mount the output volume read-write and verify with a touch probe."],"tags":["image","save","filesystem","atomic-writer","docker-cli"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}