docker/cli · error

invalid mode specified

Error message

invalid mode specified: %v

What it means

Thrown by swarmopts.SecretOpt.Set when the 'mode=<value>' field of a --secret / --secret-add secret reference cannot be parsed as a 32-bit unsigned integer. The value is parsed with strconv.ParseUint(val, 0, 32): base 0 means an unprefixed number is treated as DECIMAL, while '0o444' (octal), '0x..' (hex), or a legacy leading-zero '0444' are honored. Anything non-numeric, negative, or exceeding 2^32-1 fails here.

Solutions

  1. Prefix octal modes with 0o (Go base-0 octal): use 'mode=0o444' instead of 'mode=444'.
  2. If you really mean decimal, keep the integer in range 0..4294967295 (e.g. 'mode=292' for 0o444).
  3. Do not use symbolic chmod notation (rwx, u=rw); convert it to a numeric mode first with a umask/chmod tool.

Example fix

# before
docker service create --secret source=db,mode=444
# after
# 0o prefix => base-0 parses octal => 0o444 == r--r--r--
docker service create --secret source=db,mode=0o444
Defensive patterns

Strategy: validation

Validate before calling

// Validate the mode= field of a --secret value (Go base-0 semantics) BEFORE calling SecretOpt.Set.
import ("strconv"; "strings")

func validSecretMode(opt string) bool {
    for _, f := range strings.Split(opt, ",") {
        k, v, ok := strings.Cut(f, "=")
        if !ok || !strings.EqualFold(strings.TrimSpace(k), "mode") {
            continue
        }
        _, err := strconv.ParseUint(strings.TrimSpace(v), 0, 32) // base 0, 32-bit, like opts
        return err == nil
    }
    return true // no mode field -> default 0o444
}

// usage:
// if !validSecretMode(input) { return fmt.Errorf("bad secret mode: %s", input) }

Try / catch

// secretOpt is a pflag.Value; Set returns the error directly.
if err := secretOpt.Set(input); err != nil {
    // err already contains: invalid mode specified: <strconv error>
    return fmt.Errorf("invalid --secret value %q: %w", input, err)
}

Prevention

When it happens

Trigger: Running 'docker service create --secret source=db,mode=444' and expecting octal r--r--r-- (base 0 reads unprefixed '444' as decimal 444 = 0o674, which technically succeeds but is not what was meant). It HARD-fails on 'mode=r--r--r--', 'mode=444x', 'mode=-1', or 'mode=5000000000' (uint32 overflow).

Common situations: Users assume shell chmod-style octal/symbolic notation (e.g. 'mode=444' or 'mode=u=rw') like 'chmod'; copying a mode from a Dockerfile that used JSON '0444'; or passing a file mode larger than uint32. The base-0 quirk silently surprises anyone who expects unprefixed digits to be octal.

Related errors


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

Appendix: source

Thrown at opts/swarmopts/secret.go:61

	for _, field := range fields {
		key, val, ok := strings.Cut(field, "=")
		if !ok || key == "" {
			return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
		}
		// TODO(thaJeztah): these options should not be case-insensitive.
		switch strings.ToLower(key) {
		case "source", "src":
			options.SecretName = val
		case "target":
			options.File.Name = val
		case "uid":
			options.File.UID = val
		case "gid":
			options.File.GID = val
		case "mode":
			m, err := strconv.ParseUint(val, 0, 32)
			if err != nil {
				return fmt.Errorf("invalid mode specified: %v", err)
			}

			options.File.Mode = os.FileMode(m)
		default:
			return errors.New("invalid field in secret request: " + key)
		}
	}

	if options.SecretName == "" {
		return errors.New("source is required")
	}
	if options.File.Name == "" {
		options.File.Name = options.SecretName
	}

	o.values = append(o.values, options)
	return nil
}

View on GitHub (pinned to 4f84911bfe)