{"record":{"id":"5e1024983a858e0d","repo":"docker/cli","slug":"cannot-merge-secrets-w","errorCode":null,"errorMessage":"cannot merge secrets: %w","messagePattern":"cannot merge secrets: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/compose/loader/merge.go","lineNumber":45,"sourceCode":"}\n\nfunc merge(configs []*types.Config) (*types.Config, error) {\n\tbase := configs[0]\n\tfor _, override := range configs[1:] {\n\t\tvar errs []error\n\t\tif services, err := mergeServices(base.Services, override.Services); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"cannot merge services: %w\", err))\n\t\t} else {\n\t\t\tbase.Services = services\n\t\t}\n\t\tif err := mergo.Map(&base.Volumes, &override.Volumes, mergo.WithOverride); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"cannot merge volumes: %w\", err))\n\t\t}\n\t\tif err := mergo.Map(&base.Networks, &override.Networks, mergo.WithOverride); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"cannot merge networks: %w\", err))\n\t\t}\n\t\tif err := mergo.Map(&base.Secrets, &override.Secrets, mergo.WithOverride); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"cannot merge secrets: %w\", err))\n\t\t}\n\t\tif err := mergo.Map(&base.Configs, &override.Configs, mergo.WithOverride); err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"cannot merge configs: %w\", err))\n\t\t}\n\t\tif err := errors.Join(errs...); err != nil {\n\t\t\treturn nil, errors.Join(fmt.Errorf(\"failed to merge file %s\", override.Filename), err)\n\t\t}\n\t}\n\treturn base, nil\n}\n\nfunc mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) {\n\tmergeOpts := []func(*mergo.Config){\n\t\tmergo.WithAppendSlice,\n\t\tmergo.WithOverride,\n\t\tmergo.WithTransformers(&specials{m: map[reflect.Type]func(dst, src reflect.Value) error{\n\t\t\treflect.PointerTo(reflect.TypeFor[types.LoggingConfig]()):        safelyMerge(mergeLoggingConfig),\n\t\t\treflect.TypeFor[[]types.ServicePortConfig]():                     mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/docker/cli/blob/4f84911bfe8811e9b028e4b1fee8e7510be79387/cli/compose/loader/merge.go#L27-L63","documentation":"Raised inside the Compose multi-file merge loop when dario.cat/mergo fails to deep-merge the top-level `secrets` map of an override Compose file into the base. mergo.Map is invoked with WithOverride (merge.go:44), and any returned error is wrapped with this message. It indicates the override file's `secrets:` section has a structure incompatible with the base (e.g. a key that is a string in one file but an object in another).","triggerScenarios":"Calling the internal `merge(configs []*types.Config)` with two or more parsed Compose configs where the `Secrets` maps have conflicting value types for the same secret name. mergo.Map returns an error when it cannot merge map values of non-overridable or mismatched kinds.","commonSituations":"A base docker-compose.yml defines a secret as a short-form string (e.g. `mysecret: file`) while an override (docker-compose.override.yml) redefines the same secret as a long-form object (`mysecret: { file: ... , environment: ...}`). Mixing the v2 short syntax with v3 long syntax for the same key triggers it.","solutions":["Inspect the `secrets:` block of every Compose file passed to the merge and ensure each secret name uses the same form (both short string or both long object) across all files.","Reduce the merge set to a single file to isolate which override introduces the conflict.","Align all files to the long-form object syntax for secrets to guarantee type-consistent merges."],"exampleFix":"// before (docker-compose.yml)\nsecrets:\n  dbpass: \"./secrets/dbpass\"\n// before (docker-compose.override.yml)\nsecrets:\n  dbpass:\n    file: \"./secrets/dbpass_alt\"\n// after (both files use long form)\nsecrets:\n  dbpass:\n    file: \"./secrets/dbpass\"","handlingStrategy":"validation","validationCode":"// Before merging, verify each secret entry has a consistent type across files.\nfunc secretTypesSame(a, b map[string]any) bool {\n    for k, va := range a {\n        if vb, ok := b[k]; ok {\n            if reflect.TypeOf(va) != reflect.TypeOf(vb) {\n                return false\n            }\n        }\n    }\n    return true\n}","typeGuard":null,"tryCatchPattern":"// loader.merge returns an error; wrap to surface the section.\nmerged, err := loader.merge(configs)\nif err != nil {\n    return fmt.Errorf(\"compose merge failed: %w\", err)\n}","preventionTips":["Adopt a single canonical form (long-form objects) for all secrets across every Compose file.","Run `docker compose config` in CI to validate merged output before deploy.","Keep the number of override files small to reduce type-conflict surface."],"tags":["compose","merge","secrets","mergo"],"backgroundTag":null,"analyzedSha":"4f84911bfe8811e9b028e4b1fee8e7510be79387","analyzedAt":"2026-08-07T12:15:29.814Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}