docker/cli · error
cannot merge service
Error message
cannot merge service %s: %w
What it means
Raised during per-service merging when dario.cat/mergo.Merge fails for an individual service that exists in both the base and override Compose configs (merge.go:81-82). The service name is interpolated so the offending service is identified. Failures come from mergo being unable to reconcile fields according to the configured transformers (ports, secrets, configs, volumes, logging, ulimits, networks, etc.).
Solutions
- Open the named service in both Compose files and compare the structure of each list-valued field (ports, volumes, secrets, configs).
- Normalize list entries to the long-form object syntax in both files (e.g. `ports: [{ published: ..., target: ... }]`).
- Remove the service from the override file temporarily to confirm it is the trigger, then re-add field by field.
- Validate with `docker compose config` to surface schema-level mismatches.
Example fix
// before — short and long form mixed across files for the same service
# docker-compose.yml
services:
web:
ports: ["8080:80"]
# docker-compose.override.yml
services:
web:
ports:
- { published: "8443", target: "443" }
// after — long form everywhere
services:
web:
ports:
- { published: "8080", target: "80" } Defensive patterns
Strategy: validation
Validate before calling
// For each shared service, compare list-field element types before merging.
for _, ov := range override {
if baseSvc, ok := baseByName[ov.Name]; ok {
if reflect.TypeOf(baseSvc.Ports) != reflect.TypeOf(ov.Ports) {
return fmt.Errorf("service %s: ports field shape differs", ov.Name)
}
}
} Try / catch
services, err := loader.mergeServices(base, override)
if err != nil {
return fmt.Errorf("could not merge services: %w", err)
} Prevention
- Standardize list fields (ports, volumes, secrets, configs) to long-form object entries in all files.
- Avoid overriding the same service from more than one override file.
- Validate with `docker compose config` in CI.
When it happens
Trigger: `mergeServices` iterates override services; when a service name exists in the base map, mergo.Merge is invoked with WithAppendSlice+WithOverride and custom transformers. If a transformer or mergo itself returns an error (e.g. a slice element fails the tomap type assertion in mergeSlice), this wraps it with the service name.
Common situations: A service is defined in both docker-compose.yml and docker-compose.override.yml with a field whose slice elements cannot be mapped by the merge transformer — for example a `ports:` list containing an entry that does not satisfy the `[]types.ServicePortConfig` type assertion inside toServicePortConfigsMap, or a volume/secret list with an unexpected element shape.
Related errors
- cannot merge secrets
- cannot merge configs
- version mismatched between two composefiles
- cannot merge services
- cannot merge volumes
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/5ba7f361b391290c.
Report an issue: GitHub.
Appendix: source
Thrown at cli/compose/loader/merge.go:82
reflect.TypeFor[[]types.ServiceSecretConfig](): mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),
reflect.TypeFor[[]types.ServiceConfigObjConfig](): mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),
reflect.PointerTo(reflect.TypeFor[types.UlimitsConfig]()): mergeUlimitsConfig,
reflect.TypeFor[[]types.ServiceVolumeConfig](): mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice),
reflect.TypeFor[types.ShellCommand](): mergeShellCommand,
reflect.PointerTo(reflect.TypeFor[types.ServiceNetworkConfig]()): mergeServiceNetworkConfig,
reflect.PointerTo(reflect.TypeFor[uint64]()): mergeUint64,
}}),
}
baseServices := make(map[string]types.ServiceConfig, len(base))
for _, s := range base {
baseServices[s.Name] = s
}
for _, overrideService := range override {
if baseService, ok := baseServices[overrideService.Name]; ok {
if err := mergo.Merge(&baseService, &overrideService, mergeOpts...); err != nil {
return nil, fmt.Errorf("cannot merge service %s: %w", overrideService.Name, err)
}
baseServices[overrideService.Name] = baseService
continue
}
baseServices[overrideService.Name] = overrideService
}
services := make([]types.ServiceConfig, 0, len(baseServices))
for _, baseService := range baseServices {
services = append(services, baseService)
}
slices.SortFunc(services, func(a, b types.ServiceConfig) int {
return cmp.Compare(a.Name, b.Name)
})
return services, nil
}View on GitHub (pinned to 4f84911bfe)