You’re running a dozen services on three to five servers. Containers start via docker compose, separately on each machine. Deploying means ssh plus a script only one person really understands. When a server dies, its services stay down until someone moves them by hand.

The obvious next step is an orchestrator, and everyone says Kubernetes. But for a team of two or three developers with no dedicated platform engineer, it often becomes a new source of pain: a dozen components, its own abstraction for everything, and cluster upgrades that turn into projects of their own. A chunk of your modest servers goes to the control plane instead of your applications.

At this scale we use Nomad. It does the same job — “run these services, watch them, restart and update them without downtime” — and you can get your head around it in a few days.

What we build

A typical setup on 3–10 servers (dedicated machines at OVH or Hetzner, or VMs on Proxmox):

  • Nomad servers — three nodes using Raft consensus. They decide what runs where. A single server works too, but then it’s a single point of failure.
  • Nomad clients — the remaining machines, where your workloads actually run. On small clusters both roles often share the same nodes.
  • Consul — service catalog and health checks. Each service registers itself, and others find it by name rather than IP.
  • Traefik — the ingress proxy. It reads the Consul catalog and builds routes on its own: a new service appears and Traefik already knows where to send traffic. It also handles Let’s Encrypt certificates.
  • Secrets — Nomad Variables for simple cases, or Vault when you need dynamic database credentials, rotation and auditing.
  • Prometheus and Grafana — metrics from Nomad, Consul, Traefik and your applications, plus alerting.

We provision the machines with Terraform, configure them with Ansible, and keep the Nomad job files in the same infrastructure repository. The whole cluster is described in code and can be rebuilt from scratch.

Why Nomad, and how it works

Nomad is a single binary. Server and client are the same file; only the config differs. Upgrading means swapping the binary and restarting the service on each node in turn.

Jobs are written in HCL, which reads more easily than a pile of YAML manifests. Here’s part of a job file with a canary deployment:

group "api" {
  count = 3

  update {
    max_parallel = 1
    canary       = 1
    auto_promote = true
    auto_revert  = true
  }

  service {
    name = "shop-api"
    port = "http"
    tags = ["traefik.enable=true"]
    check {
      type     = "http"
      path     = "/health"
      interval = "10s"
      timeout  = "2s"
    }
  }
}

On update, Nomad first starts one new copy alongside the old ones. If /health passes, it replaces the rest one at a time. If the new version fails to come up, Nomad rolls back on its own. Nobody gets woken up at night to do it by hand.

Besides Docker, Nomad can run plain binaries through the exec (isolated) and raw_exec (unisolated) drivers. That helps when part of your system is an old Java service or a Go binary that isn’t worth containerising.

We usually install Traefik as a separate systemd service on the edge nodes rather than as a Nomad job. That way the way into your system doesn’t depend on the scheduler: even if the cluster is having a bad day, the proxy is still up.

Deploying from GitLab CI means building the image and running nomad job run with the new tag. Before that, nomad job plan shows exactly what will change. The CI token is scoped to its own namespace, not the whole cluster.

Trade-offs worth knowing

Smaller ecosystem. Kubernetes has a Helm chart for nearly everything. With Nomad, we often write those pieces ourselves. A 40-line job file isn’t a big deal, but it is work.

Licensing. In 2023 HashiCorp moved Nomad, Consul, Vault and Terraform from MPL to the BSL. For running them in your own infrastructure this usually changes nothing — the restrictions target people selling a competing product. Still, you should know about it. On new projects we consider OpenTofu instead of Terraform and OpenBao instead of Vault.

When you do need Kubernetes. If your team already knows k8s, if you’re heading towards dozens of servers and hundreds of services, or if you need database operators or vendor-supplied charts — go with Kubernetes. For a small cluster there’s k3s: full Kubernetes, just lighter to install. We’ll tell you plainly when Nomad is the wrong choice.

We usually keep databases outside the orchestrator, on separate machines or LXC containers with replication and backups. Nomad is great at stateless services. Data is better left out of the experiment.

What you get

  • A three-server Nomad + Consul cluster that survives losing a node.
  • Traefik at the edge with automatic certificates and routes from the Consul catalog.
  • Job files for your services in Git, with rolling or canary deploys and automatic rollback.
  • A GitLab CI pipeline: build, plan check, deploy.
  • Secrets in Nomad Variables or Vault instead of .env files on servers.
  • Prometheus and Grafana with cluster dashboards and alerts.
  • Terraform and Ansible code to rebuild the cluster from scratch.
  • Documentation: how to deploy a new service, how to upgrade the cluster, what to do when a node fails.
  • Admin access — the cluster is yours, not ours.

If your services have outgrown docker compose and Kubernetes feels like too much, get in touch. We’ll look at what you’re running and tell you whether Nomad fits — or what to use instead.