docker/cli · error

invalid --security-opt

Error message

invalid --security-opt: %q

What it means

Thrown by parseSecurityOpts when a --security-opt value cannot be split into key=value (or key:value) and is not the single valueless option 'no-new-privileges'. The CLI requires every security option except 'no-new-privileges' to carry a value. This is a client-side validation performed before the request reaches the daemon.

Solutions

  1. Add a value via '=' or ':' separator: --security-opt seccomp=unconfined or --security-opt seccomp:unconfined
  2. Use the exact bare keyword only for the valueless option: --security-opt no-new-privileges
  3. For apparmor, use --security-opt apparmor=<profile-name>

Example fix

# before
docker run --security-opt seccomp
# after
docker run --security-opt seccomp=unconfined
Defensive patterns

Strategy: validation

Validate before calling

// Validate --security-opt tokens before passing to the run/create call.
func validSecurityOpt(opt string) bool {
    if opt == "no-new-privileges" {
        return true
    }
    if _, _, ok := strings.Cut(opt, "="); ok {
        return true
    }
    if _, _, ok := strings.Cut(opt, ":"); ok {
        return true
    }
    return false
}

for _, o := range securityOpts {
    if !validSecurityOpt(o) {
        return fmt.Errorf("refusing bad --security-opt %q", o)
    }
}

Prevention

When it happens

Trigger: Calling `docker run --security-opt <bare-word>` where the token contains neither '=' nor ':', e.g. `--security-opt seccomp` or `--security-opt apparmor`. The function first tries Cut on '=', then on ':', and only 'no-new-privileges' is exempt from requiring a value.

Common situations: Forgetting the value when enabling seccomp/apparmor profiles; typoing a flag like `--security-opt no-new-privileges:true` (the colon-split makes k='no-new-privileges', v='true' which is accepted, but bare `--security-opt no-new` fails); copy-pasting partial examples from docs.

Related errors


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

Appendix: source

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

func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
	loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts)
	if loggingDriver == "none" && len(loggingOpts) > 0 {
		return map[string]string{}, fmt.Errorf("invalid logging opts for driver %s", loggingDriver)
	}
	return loggingOptsMap, nil
}

// takes a local seccomp daemon, reads the file contents for sending to the daemon
func parseSecurityOpts(securityOpts []string) ([]string, error) {
	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()
			}

View on GitHub (pinned to 4f84911bfe)