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
- Ensure every field is key=value, e.g. 'protocol=cfssl,url=https://ca.example.com'.
- Quote fields containing commas using CSV quoting (double quotes).
- 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
- Always use key=value pairs in --external-ca.
- Quote fields containing commas.
- Remember protocol and url are both required.
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
- CA cert for external CA must be in PEM format
- the external-ca option needs a protocol= parameter
- the external-ca option needs a url= parameter
- rotating to an external CA requires the
- unrecognized external CA protocol
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)