docker/cli · error

cannot merge secrets

Error message

cannot merge secrets: %w

What it means

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).

Solutions

  1. 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.
  2. Reduce the merge set to a single file to isolate which override introduces the conflict.
  3. Align all files to the long-form object syntax for secrets to guarantee type-consistent merges.

Example fix

// before (docker-compose.yml)
secrets:
  dbpass: "./secrets/dbpass"
// before (docker-compose.override.yml)
secrets:
  dbpass:
    file: "./secrets/dbpass_alt"
// after (both files use long form)
secrets:
  dbpass:
    file: "./secrets/dbpass"
Defensive patterns

Strategy: validation

Validate before calling

// Before merging, verify each secret entry has a consistent type across files.
func secretTypesSame(a, b map[string]any) bool {
    for k, va := range a {
        if vb, ok := b[k]; ok {
            if reflect.TypeOf(va) != reflect.TypeOf(vb) {
                return false
            }
        }
    }
    return true
}

Try / catch

// loader.merge returns an error; wrap to surface the section.
merged, err := loader.merge(configs)
if err != nil {
    return fmt.Errorf("compose merge failed: %w", err)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07). Data as JSON: /api/errors/5e1024983a858e0d. Report an issue: GitHub.

Appendix: source

Thrown at cli/compose/loader/merge.go:45

}

func merge(configs []*types.Config) (*types.Config, error) {
	base := configs[0]
	for _, override := range configs[1:] {
		var errs []error
		if services, err := mergeServices(base.Services, override.Services); err != nil {
			errs = append(errs, fmt.Errorf("cannot merge services: %w", err))
		} else {
			base.Services = services
		}
		if err := mergo.Map(&base.Volumes, &override.Volumes, mergo.WithOverride); err != nil {
			errs = append(errs, fmt.Errorf("cannot merge volumes: %w", err))
		}
		if err := mergo.Map(&base.Networks, &override.Networks, mergo.WithOverride); err != nil {
			errs = append(errs, fmt.Errorf("cannot merge networks: %w", err))
		}
		if err := mergo.Map(&base.Secrets, &override.Secrets, mergo.WithOverride); err != nil {
			errs = append(errs, fmt.Errorf("cannot merge secrets: %w", err))
		}
		if err := mergo.Map(&base.Configs, &override.Configs, mergo.WithOverride); err != nil {
			errs = append(errs, fmt.Errorf("cannot merge configs: %w", err))
		}
		if err := errors.Join(errs...); err != nil {
			return nil, errors.Join(fmt.Errorf("failed to merge file %s", override.Filename), err)
		}
	}
	return base, nil
}

func mergeServices(base, override []types.ServiceConfig) ([]types.ServiceConfig, error) {
	mergeOpts := []func(*mergo.Config){
		mergo.WithAppendSlice,
		mergo.WithOverride,
		mergo.WithTransformers(&specials{m: map[reflect.Type]func(dst, src reflect.Value) error{
			reflect.PointerTo(reflect.TypeFor[types.LoggingConfig]()):        safelyMerge(mergeLoggingConfig),
			reflect.TypeFor[[]types.ServicePortConfig]():                     mergeSlice(toServicePortConfigsMap, toServicePortConfigsSlice),

View on GitHub (pinned to 4f84911bfe)