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

Raised while parsing the --external-ca CSV spec when a field has no '=' separator. parseExternalCA uses strings.Cut on '='; a token like 'protocol' (instead of 'protocol=cfssl') fails the cut and yields this error.

Solutions

  1. Ensure every field is key=value, e.g. 'protocol=cfssl,url=https://ca.example.com'.
  2. Quote fields containing commas using CSV quoting (double quotes).
  3. Check required keys: protocol and url are both mandatory.

Example fix

# before
docker swarm init --external-ca protocol,url=https://ca.example.com

# after
docker swarm init --external-ca protocol=cfssl,url=https://ca.example.com
Defensive patterns

Strategy: validation

Validate before calling

// Validate each external-ca field has '='
for _, f := range strings.Split(spec, ",") {
    if !strings.Contains(f, "=") {
        return fmt.Errorf("invalid field %q: must be key=value", f)
    }
}

Type guard

func isKeyValuePair(s string) bool {
	_, _, ok := strings.Cut(s, "=")
	return ok
}

Prevention

When it happens

Trigger: Passing 'docker swarm <cmd> --external-ca protocol' (no =value), or any field in the comma-separated spec missing its '='. Also triggered by malformed CSV where a value contains an unquoted comma that splits into a bare token.

Common situations: Typo'd spec ('protocol' instead of 'protocol=cfssl'); copy-paste that lost the '='; embedded commas not quoted.

Related errors


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

Appendix: source

Thrown at cli/command/swarm/opts.go:180

	csvReader := csv.NewReader(strings.NewReader(caSpec))
	fields, err := csvReader.Read()
	if err != nil {
		return nil, err
	}

	externalCA := swarm.ExternalCA{
		Options: make(map[string]string),
	}

	var (
		hasProtocol bool
		hasURL      bool
	)

	for _, field := range fields {
		key, value, ok := strings.Cut(field, "=")
		if !ok {
			return nil, 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 "protocol":
			hasProtocol = true
			if strings.ToLower(value) == string(swarm.ExternalCAProtocolCFSSL) {
				externalCA.Protocol = swarm.ExternalCAProtocolCFSSL
			} else {
				return nil, fmt.Errorf("unrecognized external CA protocol %s", value)
			}
		case "url":
			hasURL = true
			externalCA.URL = value
		case "cacert":
			cacontents, err := os.ReadFile(value)
			if err != nil {
				return nil, fmt.Errorf("unable to read CA cert for external CA: %w", err)

View on GitHub (pinned to 4f84911bfe)