docker/cli · critical
expected a map or a list, got %T: %#v
Error message
expected a map or a list, got %T: %#v
What it means
This is a PANIC, not a returned error. transformListOrMapping (used by hosts/extra_hosts/dns list forms) only handles `map[string]any` and `[]any`; any other type calls panic(). Because Load() does not recover, the panic crashes the process. Callers must ensure the field is a map or list before it reaches the transformer.
Solutions
- Provide a list or mapping for the field, e.g. `extra_hosts: ["host1:1.2.3.4"]`.
- If calling the loader programmatically, wrap Load()/Transform() in a recover() to convert the panic into an error.
Example fix
# before
web:
extra_hosts: host1
# after
web:
extra_hosts:
- "host1:10.0.0.1" Defensive patterns
Strategy: try-catch
Validate before calling
// transformListOrMapping panics on non-map/non-list; pre-check the field.
func validateListOrMapping(field string, v any) error {
switch v.(type) {
case map[string]any, []any:
return nil
default:
return fmt.Errorf("%s must be a mapping or list, got %T", field, v)
}
} Type guard
func isMapOrList(v any) bool {
switch v.(type) {
case map[string]any, []any:
return true
}
return false
} Try / catch
// Load() does not recover; wrap it when calling programmatically.
func safeLoad(details types.ConfigDetails) (cfg *types.Config, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("compose load panic: %v", r)
}
}()
return Load(details)
} Prevention
- This error is a panic — always guard programmatic loader calls with recover().
- Ensure hosts/extra_hosts/dns-style fields are lists or maps.
- Add `docker compose config` as a CI gate to catch malformed fields.
When it happens
Trigger: A field routed through transformListOrMapping receives a scalar (e.g. `extra_hosts: host1` as a bare string) or null. The function has no default branch — it panics.
Common situations: Mis-typing a list field as a scalar; templating emitting null; downstream code calling the transformer directly with a non-conforming value.
Related errors
- invalid type %T for string list
- invalid type %T for ulimits
- invalid type %T for map[string]string
- invalid type %T for external
- invalid type %T for port
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/2f2f02f35d182ba3.
Report an issue: GitHub.
Appendix: source
Thrown at cli/compose/loader/loader.go:847
for i, allowSep := range allowSeps {
entry := fmt.Sprint(entry)
k, v, ok := strings.Cut(entry, allowSep)
if ok {
// Entry uses this allowed separator. Add it to the result, using
// sep as a separator.
result = append(result, fmt.Sprintf("%s%s%s", k, sep, v))
break
} else if i == len(allowSeps)-1 {
// No more separators to try, keep the entry if allowNil.
if allowNil {
result = append(result, k)
}
}
}
}
return result
}
panic(fmt.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping))
}
func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc {
return func(data any) (any, error) {
return transformMappingOrList(data, sep, allowNil), nil
}
}
func transformMappingOrList(mappingOrList any, sep string, allowNil bool) any {
switch values := mappingOrList.(type) {
case map[string]any:
return toMapStringString(values, allowNil)
case []any:
result := make(map[string]any)
for _, v := range values {
key, val, hasValue := strings.Cut(v.(string), sep)
switch {
case !hasValue && allowNil:View on GitHub (pinned to 4f84911bfe)