{"record":{"id":"5ba7f361b391290c","repo":"docker/cli","slug":"cannot-merge-service-s-w","errorCode":null,"errorMessage":"cannot merge service %s: %w","messagePattern":"cannot merge service (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/compose/loader/merge.go","lineNumber":82,"sourceCode":"\t\t\treflect.TypeFor[[]types.ServiceSecretConfig]():                   mergeSlice(toServiceSecretConfigsMap, toServiceSecretConfigsSlice),\n\t\t\treflect.TypeFor[[]types.ServiceConfigObjConfig]():                mergeSlice(toServiceConfigObjConfigsMap, toSServiceConfigObjConfigsSlice),\n\t\t\treflect.PointerTo(reflect.TypeFor[types.UlimitsConfig]()):        mergeUlimitsConfig,\n\t\t\treflect.TypeFor[[]types.ServiceVolumeConfig]():                   mergeSlice(toServiceVolumeConfigsMap, toServiceVolumeConfigsSlice),\n\t\t\treflect.TypeFor[types.ShellCommand]():                            mergeShellCommand,\n\t\t\treflect.PointerTo(reflect.TypeFor[types.ServiceNetworkConfig]()): mergeServiceNetworkConfig,\n\t\t\treflect.PointerTo(reflect.TypeFor[uint64]()):                     mergeUint64,\n\t\t}}),\n\t}\n\n\tbaseServices := make(map[string]types.ServiceConfig, len(base))\n\tfor _, s := range base {\n\t\tbaseServices[s.Name] = s\n\t}\n\n\tfor _, overrideService := range override {\n\t\tif baseService, ok := baseServices[overrideService.Name]; ok {\n\t\t\tif err := mergo.Merge(&baseService, &overrideService, mergeOpts...); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"cannot merge service %s: %w\", overrideService.Name, err)\n\t\t\t}\n\t\t\tbaseServices[overrideService.Name] = baseService\n\t\t\tcontinue\n\t\t}\n\t\tbaseServices[overrideService.Name] = overrideService\n\t}\n\n\tservices := make([]types.ServiceConfig, 0, len(baseServices))\n\tfor _, baseService := range baseServices {\n\t\tservices = append(services, baseService)\n\t}\n\n\tslices.SortFunc(services, func(a, b types.ServiceConfig) int {\n\t\treturn cmp.Compare(a.Name, b.Name)\n\t})\n\n\treturn services, nil\n}","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/compose/loader/merge.go#L64-L100","documentation":"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.).","triggerScenarios":"`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.","commonSituations":"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.","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."],"exampleFix":"// before — short and long form mixed across files for the same service\n# docker-compose.yml\nservices:\n  web:\n    ports: [\"8080:80\"]\n# docker-compose.override.yml\nservices:\n  web:\n    ports:\n      - { published: \"8443\", target: \"443\" }\n// after — long form everywhere\nservices:\n  web:\n    ports:\n      - { published: \"8080\", target: \"80\" }","handlingStrategy":"validation","validationCode":"// For each shared service, compare list-field element types before merging.\nfor _, ov := range override {\n    if baseSvc, ok := baseByName[ov.Name]; ok {\n        if reflect.TypeOf(baseSvc.Ports) != reflect.TypeOf(ov.Ports) {\n            return fmt.Errorf(\"service %s: ports field shape differs\", ov.Name)\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"services, err := loader.mergeServices(base, override)\nif err != nil {\n    return fmt.Errorf(\"could not merge services: %w\", err)\n}","preventionTips":["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."],"tags":["compose","merge","services","mergo","transformers"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}