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 ConfigOpt.Set (config.go:46) when a field in the CSV-parsed --config value is not a key=value pair or has an empty key. In long syntax, `docker service create --config` expects fields like source=NAME,target=/path,uid=...,mode=0440; a bare token or one missing '=' is rejected. The simple `--config NAME` shorthand only applies when there is a single field with no '='.

Solutions

  1. Write every field as key=value: `--config source=myconf,target=/etc/app/conf,mode=0440`.
  2. For the simple case use the shorthand: `--config myconf` (single token, no '=').
  3. Make sure no field is empty and each has a non-empty key before the '='.
  4. If a value is optional, still write `target=/default/path` explicitly.

Example fix

// before
--config source=myconf,target
// after
--config source=myconf,target=/etc/app/conf
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: Passing `--config source=myconf,target` (target has no value/equals), `--config foo,bar` where bar lacks '=', `--config =x` (empty key), or a CSV field containing only whitespace. strings.Cut at line 44 returns ok=false or key="".

Common situations: Mixing positional and key=value syntax, forgetting the '=' for the target field, copy-paste truncation, or building the --config string via comma-join with a missing value.

Related errors


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

Appendix: source

Thrown at opts/swarmopts/config.go:46

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

	// support a simple syntax of --config foo
	if len(fields) == 1 && !strings.Contains(fields[0], "=") {
		options.File.Name = fields[0]
		options.ConfigName = 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.ConfigName = 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)
			}

View on GitHub (pinned to 4f84911bfe)