> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Service Discovery With Nomad and Consul

> Explains service discovery using Nomad and Consul, configuring services and health checks, differences in health filtering, and when to use Nomad built-in registry versus Consul

Hard-coding an API's IP and port into a config file works only as long as the service never moves. Tidepool’s web frontend initially used a single static address for its API — fine when the API lived on one stable machine, but brittle once services are relocated, restarted, or scaled.

A more resilient approach is to stop asking “Where is it?” and instead ask “Who is it?” — register each API copy under a logical name, and have clients look up that name to get the currently alive addresses. As instances start and stop, the registry’s results change automatically and client configs stay unchanged.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/l4I9ipyyKXTd2i-D/images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/who-is-it-not-where-naming.jpg?fit=max&auto=format&n=l4I9ipyyKXTd2i-D&q=85&s=cb974c205fd2f0d328b669d161710bcd" alt="A presentation slide titled &#x22;Who Is It, Not Where&#x22; showing icons for a document, a Docker container (whale), and a magnifying glass with captions &#x22;one IP, typed once,&#x22; &#x22;copies that moved,&#x22; and &#x22;ask by name.&#x22; Below are four server/container illustrations labeled box-1 through box-4." width="1920" height="1080" data-path="images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/who-is-it-not-where-naming.jpg" />
</Frame>

## How Nomad exposes services

In Nomad, service discovery is configured inside the `group` block of a job file via a `service` stanza. Each service includes three core properties:

* `name` — the logical name that clients will query (e.g., `tidepool-api`).
* `port` — a port *label* that refers to a port declared in the job’s `network` block (not a literal number).
* `provider` — the registry that manages service entries (Nomad’s built-in registry or Consul).

Always reference the port label (for example, `http`) defined in the `network` block so Nomad can assign ports dynamically at runtime. Do not hard-code numeric ports inside the `service` block.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/l4I9ipyyKXTd2i-D/images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/service-block-port-label-docker-icons.jpg?fit=max&auto=format&n=l4I9ipyyKXTd2i-D&q=85&s=022a41fdc0ae7912d2b61e5e11274280" alt="A presentation slide titled &#x22;The service Block&#x22; with icons of a document and a plug and the statement &#x22;Port is a label, not a number.&#x22; Below are Docker whale and server icons arranged over four labeled boxes: box-1 to box-4." width="1920" height="1080" data-path="images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/service-block-port-label-docker-icons.jpg" />
</Frame>

### Example Nomad job (HCL)

This example shows a minimal job that registers an `http`-labelled service with Nomad’s registry and attaches an HTTP health check.

```hcl theme={null}
job "tidepool" {
  datacenters = ["dc1"]

  group "api" {
    count = 3

    network {
      port "http" {}
    }

    task "api" {
      driver = "docker"
      config {
        image = "tidepool/api:latest"
        ports = ["http"]
      }

      resources {
        network {
          port "http" {}
        }
      }
    }

    service {
      name     = "tidepool-api"
      port     = "http"
      provider = "nomad" # Use "nomad" explicitly for Nomad's built-in registry

      check {
        type     = "http"
        path     = "/healthz"
        interval = "10s"
        timeout  = "2s"
      }

      # Optional: restart the task after consecutive check failures
      # check_restart {
      #   limit = 3
      # }
    }
  }
}
```

<Callout icon="lightbulb" color="#1CB2FE">
  If you omit `provider`, Nomad assumes `consul` by default. Explicitly set `provider = "nomad"` to use Nomad’s built-in service registry.
</Callout>

## Health checks: who is actually healthy?

Registering a service name is only part of the story — clients need to know which instances are healthy. That’s why the `service` block supports a nested `check` stanza. The `check` must be nested directly under `service` (not at top-level or inside `task`).

Common check properties:

| Field | Purpose | Example |
| - | - | - |
| `type` | Type of probe (usually `http` or `tcp`) | `http` |
| `path` | HTTP path to probe (only for `http` checks) | `/healthz` |
| `interval` | How often Nomad probes the instance | `10s` |
| `timeout` | How long to wait for a response | `2s` |

Nomad will call the check endpoint repeatedly for every service copy. This gives visibility into instance health so operators can act.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/l4I9ipyyKXTd2i-D/images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/check-block-nested-docker-boxes.jpg?fit=max&auto=format&n=l4I9ipyyKXTd2i-D&q=85&s=8a186214b86115e1dfaf7304bfd848e4" alt="A presentation slide titled &#x22;The check Block, Nested&#x22; showing icons (a shield with a check, a folder, and a stopwatch) and the phrase &#x22;Inside service, nowhere else.&#x22; Along the bottom are Docker whale icons above server racks labeled box-1 through box-4 with green check marks." width="1920" height="1080" data-path="images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/check-block-nested-docker-boxes.jpg" />
</Frame>

## Nomad built-in provider: visibility vs. filtering

One important caveat with Nomad’s built-in provider: it records and shows check failures, but it does not filter out failing instances from the service listing. That means a copy with failing checks can still appear in client query results.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/l4I9ipyyKXTd2i-D/images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/no-health-filtering-bell-servers-passed.jpg?fit=max&auto=format&n=l4I9ipyyKXTd2i-D&q=85&s=65f2d70d5f8591fea07ebc2e8e6c623b" alt="A slide titled &#x22;No Health Filtering&#x22; showing a ringing bell and magnifying-glass icon with the text &#x22;Inside service, nowhere else.&#x22; Below are four server boxes labeled box-1 to box-4 with Docker whale icons and green check marks indicating they passed checks." width="1920" height="1080" data-path="images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/no-health-filtering-bell-servers-passed.jpg" />
</Frame>

So what does a failing check accomplish if the unhealthy copy remains listed? It provides visibility and the option to automate recovery:

* Visibility: operators can see which instances are failing and investigate.
* Automatic restarts: you can configure a `check_restart` block to restart tasks after N consecutive failures. Note that `check_restart.limit` defaults to `0` (disabled).

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/l4I9ipyyKXTd2i-D/images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/check-restart-defaults-off-10min.jpg?fit=max&auto=format&n=l4I9ipyyKXTd2i-D&q=85&s=4b18bc7d9035b8de000f396c524dc26b" alt="A presentation slide titled &#x22;check_restart Defaults to Off&#x22; showing icons (bell, magnifier, stopwatch) and text explaining that a failed check stays in results and causes &#x22;Errors for ten minutes.&#x22; The bottom shows four checked boxes with server and Docker icons labeled box-1 through box-4." width="1920" height="1080" data-path="images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/check-restart-defaults-off-10min.jpg" />
</Frame>

<Callout icon="warning" color="#FF6B6B">
  Be careful: `check_restart.limit` defaults to `0` (off). If you add a check but do not set a restart limit, a permanently broken instance may remain listed and continue to receive traffic until someone intervenes.
</Callout>

## Why choose Consul?

Because the built-in provider does not filter unhealthy instances by default, many teams prefer Consul for service discovery. Consul offers additional capabilities:

* Health filtering by default — Consul returns only instances whose checks are passing.
* DNS-based discovery — clients that resolve hostnames can use Consul without Nomad-specific logic.
* Optional service proxying and mTLS — secure, authenticated communication between services.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/l4I9ipyyKXTd2i-D/images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/consul-adds-visibility-restart-docker-boxes.jpg?fit=max&auto=format&n=l4I9ipyyKXTd2i-D&q=85&s=20a0d955d4f3e3e963a35f24a535cf2e" alt="A presentation slide titled &#x22;What Consul Adds&#x22; showing icons for a shield (visibility by default) and a gear (restart if you ask). Below are four illustrated boxes with Docker whale and server icons labeled box-1 through box-4." width="1920" height="1080" data-path="images/Learn-By-Doing-HashiCorp-Nomad/Scaling-and-Connecting-Workloads/Service-Discovery-With-Nomad-and-Consul/consul-adds-visibility-restart-docker-boxes.jpg" />
</Frame>

## Which provider should you use?

| Scenario | Recommended provider |
| - | - |
| All callers and services run inside the same Nomad cluster and clients implement retries | Nomad built-in provider (`provider = "nomad"`) |
| You need failing instances filtered from results, DNS-based resolution, or service identity/mTLS | Consul (`provider = "consul"`) |

Remember that Consul is additional infrastructure: you must operate Consul servers, handle upgrades, and plan for its availability.

In Tidepool’s case, Maya continued to use Nomad’s built-in registry because the frontend already implements retries: a bad copy costs a retry, not an outage. Regardless of provider, the static two-year-old address was removed and replaced by a dynamic discovery model that scales and survives instance churn.

There is one more block in that configuration file that we haven't touched yet.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/learn-by-doing-hashicorp-nomad/module/d6f53825-444d-461b-adca-bce23263dba7/lesson/b2c5272f-6fc6-478a-b259-2dfbbadc4335" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.