hashicorp/nomad · error

service %q is duplicate

Error message

service %q is duplicate

What it means

Within one task group, two services may not share the same name AND port label. Nomad keys registered services by name+port; a duplicate would produce ambiguous Consul registrations, so validation rejects it with this error. The dedup map `knownServices[name+portLabel]` detects the second occurrence.

Source

Thrown at nomad/structs/structs.go:8496

	knownServices := make(map[string]struct{})
	for i, service := range t.Services {
		if err := service.Validate(); err != nil {
			outer := fmt.Errorf("service[%d] %+q validation failed: %s", i, service.Name, err)
			mErr.Errors = append(mErr.Errors, outer)
		}

		if service.AddressMode == AddressModeAlloc {
			mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc\", only services defined in a \"group\" block can use this mode", service.Name))
		}

		if service.AddressMode == AddressModeAllocIPv6 {
			mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q cannot use address_mode=\"alloc_ipv6\", only services defined in a \"group\" block can use this mode", service.Name))
		}

		// Ensure that services with the same name are not being registered for
		// the same port
		if _, ok := knownServices[service.Name+service.PortLabel]; ok {
			mErr.Errors = append(mErr.Errors, fmt.Errorf("service %q is duplicate", service.Name))
		}
		knownServices[service.Name+service.PortLabel] = struct{}{}

		if service.PortLabel != "" {
			if service.AddressMode == "driver" {
				// Numeric port labels are valid for address_mode=driver
				_, err := strconv.Atoi(service.PortLabel)
				if err != nil {
					// Not a numeric port label, add it to list to check
					addServicePort(service.PortLabel, service.Name)
				}
			} else {
				addServicePort(service.PortLabel, service.Name)
			}
		}

		// connect block is only allowed on group level
		if service.Connect != nil {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Rename one of the duplicate service blocks to a unique name (e.g. suffix with the task name).
  2. Point one duplicate at a different port label if they were meant to expose distinct endpoints.
  3. Delete the redundant service stanza if it is a copy-paste artifact.
  4. Search the job file for repeated `service { name = ...` entries sharing both name and port.

Example fix

// before
group "web" {
  service { name = "http"; port = "8080" }
  service { name = "http"; port = "8080" }
}

// after
group "web" {
  service { name = "http"; port = "8080" }
  service { name = "http-admin"; port = "8081" }
}
Defensive patterns

Strategy: validation

Validate before calling

seen := map[string]bool{}
for _, tg := range job.TaskGroups {
  for _, t := range tg.Tasks {
    for _, s := range t.Services {
      key := s.Name + s.PortLabel
      if seen[key] {
        return fmt.Errorf("duplicate service %q on port %q in group %q", s.Name, s.PortLabel, tg.Name)
      }
      seen[key] = true
    }
  }
}

Prevention

When it happens

Trigger: Submitting a job whose group (including all nested tasks) defines two service blocks with identical `name` and identical `port`/port_label; note the key is the concatenation name+portLabel, so same name on different ports is allowed.

Common situations: Template-generated jobs that emit a service stanza per task with hardcoded names; copy-pasting a service block and forgetting to change the name; two tasks in one group exposing the same port with the same service name.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/f491e827d07fc368. Report an issue: GitHub.