DEV Community

Cover image for Nomad vs. Kubernetes: Why We Switched Our SaaS to HashiCorp Nomad
Ameer Hamza
Ameer Hamza

Posted on

Nomad vs. Kubernetes: Why We Switched Our SaaS to HashiCorp Nomad

The Complexity Tax: Why Kubernetes Isn't Always the Answer

In the modern DevOps landscape, Kubernetes (K8s) is often treated as the default choice for container orchestration. It's powerful, battle-tested, and has a massive ecosystem. However, for many small-to-medium SaaS teams, Kubernetes comes with a significant "complexity tax."

We spent two years managing a production K8s cluster. While it solved our scaling issues, we found ourselves spending 30% of our engineering time just maintaining the orchestrator itself—debugging CNI plugins, managing complex RBAC, and wrestling with Helm charts that felt like they required a PhD to understand.

That's when we looked at HashiCorp Nomad.

Nomad is a lightweight, flexible orchestrator that can manage both containerized and non-containerized applications. It follows the Unix philosophy: do one thing and do it well. In this article, I’ll walk you through why we made the switch, the architectural differences, and how you can implement a production-ready Nomad workflow.


Architecture: Simplicity by Design

Unlike Kubernetes, which is a collection of loosely coupled components (etcd, API server, scheduler, controller manager, etc.), Nomad is a single binary. It handles scheduling and resource management, while delegating service discovery to Consul and secrets management to Vault.

The "Single Binary" Advantage

In Kubernetes, setting up a high-availability cluster from scratch is a daunting task. In Nomad, you simply run the binary with a configuration file.

# A basic Nomad agent configuration
data_dir = "/opt/nomad/data"
bind_addr = "0.0.0.0"

server {
  enabled          = true
  bootstrap_expect = 3
}

client {
  enabled = true
}
Enter fullscreen mode Exit fullscreen mode

This simplicity extends to the developer experience. A Nomad "Job" is defined in HCL (HashiCorp Configuration Language), which is far more readable than the verbose YAML required by Kubernetes.


Deep-Dive Implementation: Moving to Nomad

1. Defining Your First Job

In Nomad, the hierarchy is Job -> Group -> Task. A Job can contain multiple groups, and a group contains tasks that are co-located on the same node (similar to a K8s Pod).

Here is a production-ready job file for a Next.js frontend:

job "webapp" {
  datacenters = ["dc1"]
  type        = "service"

  group "frontend" {
    count = 3

    network {
      port "http" {
        to = 3000
      }
    }

    service {
      name = "webapp-frontend"
      port = "http"

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

    task "nextjs" {
      driver = "docker"

      config {
        image = "my-registry/webapp:latest"
        ports = ["http"]
      }

      resources {
        cpu    = 500 # MHz
        memory = 256 # MB
      }

      env {
        NODE_ENV = "production"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Service Discovery with Consul

One of the "gotchas" in Kubernetes is the complexity of Ingress controllers. In the Nomad ecosystem, you use Consul. When a Nomad task starts, it automatically registers itself with Consul. You can then use Fabio or Traefik as a load balancer that dynamically updates its configuration based on Consul's service catalog.

3. Handling Secrets with Vault

Instead of K8s Secrets (which are just base64 encoded strings), Nomad integrates natively with Vault. You can inject secrets directly into your environment variables or as files:

template {
  data = <<EOH
    DATABASE_URL="{{with secret "database/creds/readonly"}}{{.Data.url}}{{end}}"
  EOH
  destination = "secrets/file.env"
  env         = true
}
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls & Edge Cases

The "No-Overlay" Networking Trap

By default, Nomad uses the host's network stack. While this provides near-native performance, it means you have to manage port collisions.
The Fix: Use Nomad's bridge networking mode with CNI plugins if you need isolated container networks similar to K8s.

State Management

Nomad's CSI (Container Storage Interface) support is excellent, but it's not as "automagical" as K8s.
The Fix: For SaaS databases, we recommend running them on managed services (like RDS or Supabase) or dedicated nodes using Nomad's host_volume for maximum IOPS.


Comparison: Nomad vs. Kubernetes

Feature Kubernetes HashiCorp Nomad
Complexity High (Steep learning curve) Low (Single binary)
Flexibility Containers only (mostly) Containers, Binaries, Java, VMs
Ecosystem Massive Focused (HashiStack)
Resource Usage High overhead Very low overhead

Conclusion

Switching to Nomad allowed our team to stop "managing the orchestrator" and start "shipping features" again. It’s not that Kubernetes is bad—it’s just that for many use cases, it’s overkill.

Key Takeaways:

  • Nomad is significantly easier to operate and maintain.
  • The HashiStack (Nomad, Consul, Vault) provides a modular, best-of-breed approach.
  • HCL is a superior configuration language for infrastructure-as-code.

What's your approach to orchestration? Have you felt the "Kubernetes fatigue," or do you think the ecosystem benefits outweigh the complexity? Drop your thoughts in the comments.


About the Author: Ameer Hamza is a Top-Rated Full-Stack Developer with 7+ years of experience building SaaS platforms, eCommerce solutions, and AI-powered applications. He specializes in Laravel, Vue.js, React, Next.js, and AI integrations — with 50+ projects shipped and a 100% job success rate. Check out his portfolio at ameer.pk to see his latest work, or reach out for your next development project.

Top comments (0)