docker/cli · error

invalid field ' ' must be a key=value pair

Error message

invalid field '%s' must be a key=value pair

What it means

Thrown by SecretOpt.Set (secret.go:46) when a field in the long-syntax --secret value is not a key=value pair or has an empty key. Mirrors the config logic: long syntax expects source=NAME,target=/path,uid=...,gid=...,mode=0440; a bare token or missing '=' is rejected. The simple `--secret NAME` shorthand only applies for a single field with no '='.

Solutions

  1. Write every field as key=value: `--secret source=db_pw,target=/run/secrets/db,mode=0400`.
  2. For the simple case use shorthand: `--secret db_pw`.
  3. Ensure each field has a non-empty key before '='.
  4. If a value is optional, still provide it explicitly (e.g. target=/default/path).

Example fix

// before
--secret source=db_pw,target
// after
--secret source=db_pw,target=/run/secrets/db_pw
Defensive patterns

Strategy: validation

Validate before calling

// Verify every CSV field of a --secret value is key=value with a non-empty key.
for _, f := range strings.Split(secretVal, ",") {
    k, _, ok := strings.Cut(strings.TrimSpace(f), "=")
    if !ok || k == "" {
        return fmt.Errorf("secret field %q must be key=value", f)
    }
}

Prevention

When it happens

Trigger: Passing `--secret source=db_pw,target` (target lacks value), `--secret foo,bar` (bar has no '='), `--secret =x` (empty key). strings.Cut at line 44 returns ok=false or empty key.

Common situations: Mixing positional and key=value syntax, forgetting '=' for target/mode, copy-paste truncation, or building the --secret string with a missing value.

Related errors


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

Appendix: source

Thrown at opts/swarmopts/secret.go:46

		File: &swarm.SecretReferenceFileTarget{
			UID:  "0",
			GID:  "0",
			Mode: 0o444,
		},
	}

	// support a simple syntax of --secret foo
	if len(fields) == 1 && !strings.Contains(fields[0], "=") {
		options.File.Name = fields[0]
		options.SecretName = fields[0]
		o.values = append(o.values, options)
		return nil
	}

	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)

View on GitHub (pinned to 4f84911bfe)