docker/cli · error

opening seccomp profile

Error message

opening seccomp profile (%s) failed: %w

What it means

Thrown by parseSecurityOpts when seccomp=<filename> is used and os.ReadFile cannot read that file. The CLI reads the local seccomp profile file to send its JSON content to the daemon, so the file must exist and be readable by the user running docker.

Solutions

  1. Verify the path exists and is a file: ls -l /path/to/profile.json
  2. Use an absolute path to avoid working-directory ambiguity
  3. Fix permissions so the user invoking docker can read it (chmod +r or adjust ownership)
  4. If the file is under a restricted home dir and you run via sudo, copy it to a world-readable location

Example fix

# before
docker run --security-opt seccomp=~/profiles/custom.json ...
# after
docker run --security-opt seccomp=/home/user/profiles/custom.json ...
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the seccomp profile file is readable before invoking docker.
profile := "/path/to/profile.json"
info, err := os.Stat(profile)
if err != nil || info.IsDir() {
    return fmt.Errorf("seccomp profile not readable: %w", err)
}
// optional: attempt a read to confirm permissions
if _, err := os.ReadFile(profile); err != nil {
    return err
}

Prevention

When it happens

Trigger: Calling `docker run --security-opt seccomp=/path/to/profile.json` where the path does not exist, is a directory, or the invoking user lacks read permission. os.ReadFile returns an os.PathError wrapped into this error.

Common situations: Relative path that resolves against an unexpected working directory; profile deleted/moved; SELinux/AppArmor blocking access; misspelled path; running docker via sudo where the file is only readable by the non-root user.

Related errors


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

Appendix: source

Thrown at cli/command/container/opts.go:948

	for key, opt := range securityOpts {
		k, v, ok := strings.Cut(opt, "=")
		if !ok && k != "no-new-privileges" {
			k, v, ok = strings.Cut(opt, ":")
		}
		if (!ok || v == "") && k != "no-new-privileges" {
			// "no-new-privileges" is the only option that does not require a value.
			return securityOpts, fmt.Errorf("invalid --security-opt: %q", opt)
		}
		if k == "seccomp" {
			switch v {
			case seccompProfileDefault, seccompProfileUnconfined:
				// known special names for built-in profiles, nothing to do.
			default:
				// value may be a filename, in which case we send the profile's
				// content if it's valid JSON.
				f, err := os.ReadFile(v)
				if err != nil {
					return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %w", v, err)
				}
				var b bytes.Buffer
				if err := json.Compact(&b, f); err != nil {
					return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %w", v, err)
				}
				securityOpts[key] = "seccomp=" + b.String()
			}
		}
	}

	return securityOpts, nil
}

// parseSystemPaths checks if `systempaths=unconfined` security option is set,
// and returns the `MaskedPaths` and `ReadonlyPaths` accordingly. An updated
// list of security options is returned with this option removed, because the
// `unconfined` option is handled client-side, and should not be sent to the
// daemon.

View on GitHub (pinned to 4f84911bfe)