hashicorp/nomad · error

Services are not unique: %s

Error message

Services are not unique: %s

What it means

During TaskGroup validation in nomad/structs/structs.go, Nomad collects services whose <task, name, port> tuple is duplicated within the group and fails validation. Job specs must have unique service identity per task/name/port combination so that service registration and discovery are unambiguous.

Source

Thrown at nomad/structs/structs.go:7559

		for _, check := range service.Checks {
			if check.TaskName != "" {
				if check.AddressMode == AddressModeDriver {
					mErr.Errors = append(mErr.Errors, fmt.Errorf("Check %q invalid: cannot use address_mode=\"driver\", only checks defined in a \"task\" service block can use this mode", service.Name))
				}
				if !taskSet.Contains(check.TaskName) {
					mErr.Errors = append(mErr.Errors,
						fmt.Errorf("Check %s invalid: refers to non-existent task %s", check.Name, check.TaskName))
				}
			}
		}
	}

	// Produce an error of any services which are not unique enough in the group
	// i.e. have same <task, name, port>
	if idDuplicateSet.Size() > 0 {
		mErr.Errors = append(mErr.Errors,
			fmt.Errorf(
				"Services are not unique: %s",
				idDuplicateSet.StringFunc(
					func(u unique) string {
						s := u.task + "->" + u.name
						if u.port != "" {
							s += ":" + u.port
						}
						return s
					},
				),
			),
		)
	}

	// The initial feature release of native service discovery only allows for
	// a single service provider to be used across all services in a task
	// group.
	if providerSet.Size() > 1 {

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Rename one of the duplicate services so each <task, name, port> tuple is unique
  2. If you only need more checks, move the extra checks into the existing service's `check` blocks instead of duplicating the service
  3. Parameterize your template/generator so service names include the task or port they target

Example fix

// before
service { name = "web" port = "http" }
service { name = "web" port = "http" }
// after
service {
  name = "web"
  port = "http"
  check { type = "http" path = "/health" }
}
Defensive patterns

Strategy: validation

Validate before calling

names := map[string]bool{}
for _, svc := range group.Services {
  key := svc.Task + "->" + svc.Name + ":" + svc.Port
  if names[key] { return fmt.Errorf("duplicate service %q", key) }
  names[key] = true
}

Type guard

func uniqueServices(svcs []*api.Service) bool {
  seen := map[string]bool{}
  for _, s := range svcs {
    k := s.TaskName + "->" + s.Name + ":" + s.PortLabel
    if seen[k] { return false }
    seen[k] = true
  }
  return true
}

Prevention

When it happens

Trigger: Submitting a job where a task group (or its tasks) declares two services with the same name pointing at the same task and the same port label — e.g. two `service { name = "http" port = "http" }` blocks attached to the same task.

Common situations: Copy-pasting a service block to add a second check instead of adding checks to the existing service; templating loops that generate services with an identical name; forgetting that group-level and task-level services share the uniqueness constraint.

Related errors


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