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

  1. Open the named service in both Compose files and compare the structure of each list-valued field (ports, volumes, secrets, configs).
  2. Normalize list entries to the long-form object syntax in both files (e.g. `ports: [{ published: ..., target: ... }]`).
  3. Remove the service from the override file temporarily to confirm it is the trigger, then re-add field by field.
  4. 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

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


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)