What is the Kubernetes to Nomad Migration?
Migrating from Kubernetes to HashiCorp Nomad is the process of transitioning your containerized and non-containerized workloads from the complex, feature-rich Kubernetes orchestration platform to Nomad's simpler, more lightweight scheduling system. This migration involves translating Kubernetes primitives—such as Pods, Deployments, Services, ConfigMaps, and Ingresses—into Nomad equivalents like Jobs, Task Groups, Services, and Templates. Nomad is designed to handle both container workloads (Docker, Podman) and non-containerized workloads (raw executables, Java JARs, batch scripts) with equal ease, all within a single cluster. The migration typically targets organizations seeking reduced operational complexity, lower infrastructure overhead, or a scheduler that integrates natively with the HashiCorp ecosystem (Vault, Consul, Terraform) without requiring additional tooling.
Why Migrating to Nomad Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Kubernetes is undeniably powerful, but its complexity can become a significant burden for teams that don't need its full feature set. Understanding why a migration to Nomad matters helps you evaluate whether this transition aligns with your engineering goals:
Radical Operational Simplicity
Nomad is a single binary for both clients and servers. There are no separate controller managers, scheduler components, API servers, or etcd clusters to maintain. A fully functional Nomad cluster can be bootstrapped in minutes with minimal configuration, whereas Kubernetes requires careful provisioning of multiple interdependent components. This simplicity reduces the cognitive load on operations teams and minimizes failure domains.
Lower Infrastructure Overhead
Running Kubernetes requires etcd, which demands dedicated resources, careful backup strategies, and specific tuning. Nomad uses Raft for consensus (embedded within its server process) and stores state on disk. The overall resource footprint of a Nomad cluster is dramatically smaller—often 10-20% of a comparable Kubernetes control plane—freeing compute resources for actual workloads.
Multi-Workload Orchestration
Kubernetes fundamentally operates on containers. If you need to schedule a raw binary, a standalone JAR file, or a batch processing script, you must wrap it in a container image, build it, push it to a registry, and maintain that pipeline. Nomad supports Docker, Podman, QEMU, Java, raw_exec, and isolated fork/exec drivers natively. You can schedule a Go binary, a Python script, or a Windows executable directly without containerization overhead.
Native HashiCorp Ecosystem Integration
Nomad integrates with Vault for secret management and Consul for service discovery and service mesh networking out of the box. There's no need to install third-party operators or sidecar injectors—these integrations are first-class features enabled with a few configuration lines.
Predictable Scheduling Throughput
Nomad's scheduler is designed for high throughput with deterministic placement. For organizations running thousands of batch jobs or CI/CD pipelines, Nomad can handle massive scheduling rates without the tuning complexity that Kubernetes requires at scale.
Pre-Migration Planning
Before writing a single Nomad job specification, invest time in understanding your current Kubernetes workloads and mapping them to Nomad's conceptual model. This planning phase prevents costly rework later.
Inventory Your Kubernetes Resources
Start by cataloging every resource in your Kubernetes namespaces. Focus especially on:
- Deployments and StatefulSets — These will become Nomad Jobs with service or batch schedulers.
- Services — These map to Nomad Service blocks within jobs, registered with Consul.
- ConfigMaps and Secrets — ConfigMaps become Nomad Template stanzas; Secrets become Vault paths referenced in templates.
- Ingress Resources — These require an external load balancer (Traefik, HAProxy, Nginx) configured to route to Consul service names.
- PersistentVolumeClaims — Nomad supports host volumes, CSI volumes, and mount options on a per-task basis.
- CronJobs — Nomad supports periodic jobs natively with cron syntax.
- DaemonSets — Nomad's system scheduler type places jobs on every eligible node.
Map Kubernetes Concepts to Nomad
The conceptual translation table below provides a reference during migration:
Kubernetes Concept → Nomad Equivalent
─────────────────────────────────────────────────────
Pod → Task Group (co-located tasks)
Container → Task
Deployment → Job (type: "service")
StatefulSet → Job (type: "service") with static volumes
DaemonSet → Job (type: "system")
CronJob → Job (type: "periodic")
Service → Service stanza in Job (Consul registration)
ConfigMap → Template stanza with embedded data
Secret → Vault path + Template stanza
Ingress → External LB routing to Consul services
Namespace → Nomad namespace (enterprise) or job name prefix
PersistentVolumeClaim → Volume stanza with CSI or host volume
Init Container → Task with lifecycle "init" or "prestart"
Sidecar Container → Additional task in the same task group
HorizontalPodAutoscaler → Not built-in; use external metrics + API scaling
PodDisruptionBudget → Not built-in; use job constraints and spread
ServiceAccount → Vault identity or task-level ACL token
Step-by-Step Migration Guide
The following steps walk through a real-world migration from Kubernetes to Nomad, converting a typical microservice application with a web server, a backend API, and a periodic cleanup job.
Step 1: Set Up a Nomad Cluster
Begin by deploying a Nomad cluster. For development or evaluation, a single-node cluster running in dev mode is sufficient. For production, configure at least three server nodes and multiple client nodes.
# On a server node (bootstrap the cluster)
sudo nomad agent -server -bootstrap-expect=3 \
-data-dir=/var/lib/nomad \
-config=/etc/nomad/server.hcl
# On client nodes
sudo nomad agent -client \
-data-dir=/var/lib/nomad \
-config=/etc/nomad/client.hcl \
-servers=nomad-server-1:4647,nomad-server-2:4647,nomad-server-3:4647
A minimal server configuration file looks like this:
# /etc/nomad/server.hcl
data_dir = "/var/lib/nomad"
server {
enabled = true
bootstrap_expect = 3
}
advertise {
http = "192.168.1.10"
rpc = "192.168.1.10"
serf = "192.168.1.10"
}
Verify cluster health with nomad node status and nomad server members.
Step 2: Install and Configure Consul for Service Discovery
Nomad natively integrates with Consul. Install Consul alongside Nomad (often on the same nodes) to enable service discovery and health checking.
# Start Consul agent
consul agent -dev -bind=192.168.1.10 -client=0.0.0.0
In the Nomad client configuration, point to the local Consul agent:
# /etc/nomad/client.hcl
data_dir = "/var/lib/nomad"
client {
enabled = true
servers = ["192.168.1.10:4647"]
}
consul {
address = "127.0.0.1:8500"
}
Step 3: Convert a Kubernetes Deployment to a Nomad Job
Consider a typical Kubernetes Deployment for an Nginx web server:
# Original Kubernetes Deployment YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
env:
- name: ENVIRONMENT
value: "production"
resources:
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 10
periodSeconds: 5
The equivalent Nomad job specification translates every element:
# Nomad Job: web-app.nomad
job "web-app" {
datacenters = ["dc1"]
type = "service"
group "web" {
count = 3
network {
port "http" {
to = 80
}
}
service {
name = "web-app"
port = "http"
tags = ["production", "nginx"]
check {
type = "http"
path = "/health"
port = "http"
interval = "10s"
timeout = "2s"
}
}
task "nginx" {
driver = "docker"
config {
image = "nginx:1.25"
ports = ["http"]
}
env {
ENVIRONMENT = "production"
}
resources {
cpu = 500
memory = 256
}
}
}
}
Submit the job to Nomad:
nomad job run web-app.nomad
nomad job status web-app
Step 4: Convert ConfigMaps to Nomad Templates
Kubernetes ConfigMaps inject configuration data into pods via environment variables or mounted volumes. Nomad's template stanza accomplishes both patterns using Consul data or inline content.
# Original Kubernetes ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
database.url: "postgres://db.internal:5432/mydb"
log.level: "info"
In Nomad, embed configuration directly using templates:
# Inside a Nomad task definition
task "api-server" {
driver = "docker"
config {
image = "myapp:2.0"
}
template {
data = <
For dynamic values stored in Consul, reference the key-value store:
template {
data = <
Step 5: Convert Kubernetes Secrets to Vault-Backed Templates
Kubernetes Secrets are base64-encoded values stored in etcd. In Nomad, secrets management is delegated to HashiCorp Vault. This provides encryption at rest, dynamic secrets, and audit logging.
# Enable Vault integration in Nomad client config
vault {
enabled = true
address = "http://127.0.0.1:8200"
token = "s.your-vault-token-here"
}
Reference Vault secrets in job templates:
task "api-server" {
driver = "docker"
vault {
policies = ["api-server-policy"]
}
template {
data = <
This approach is more secure than Kubernetes Secrets—values are never stored in Nomad state, never base64-encoded in manifests, and are pulled fresh from Vault on each task start.
Step 6: Convert Services and Ingress
Kubernetes Services provide stable DNS-based discovery. Nomad uses Consul for service registration, which provides both DNS and HTTP-based discovery.
# Kubernetes Service equivalent in Nomad
# Add a service block to your job group:
service {
name = "api-server"
port = "api-port"
tags = ["v2", "production"]
connect {
native = true # If the app speaks Consul Connect natively
}
check {
type = "http"
path = "/healthz"
port = "api-port"
interval = "15s"
timeout = "3s"
}
}
For ingress, Kubernetes Ingress resources don't have a direct Nomad equivalent. Instead, deploy an edge load balancer like Traefik as a Nomad job itself, and configure it to route to Consul service names:
# Traefik Nomad job snippet with Consul integration
job "traefik" {
type = "system" # Run on every node for ingress
group "edge" {
network {
port "http" {
static = 80
}
port "https" {
static = 443
}
}
task "traefik" {
driver = "docker"
config {
image = "traefik:v3.0"
args = [
"--providers.consulCatalog=true",
"--providers.consulCatalog.exposedByDefault=false",
"--entrypoints.web.address=:80",
"--entrypoints.websecure.address=:443",
]
}
}
}
}
Then tag Nomad services with traefik.enable=true and routing tags to expose them through Traefik automatically.
Step 7: Convert StatefulSets and Persistent Storage
Kubernetes StatefulSets provide stable pod identities and persistent volumes. Nomad achieves similar behavior through host_volume declarations, CSI plugins, or ephemeral_disk with mount configurations.
# Kubernetes StatefulSet volume claim becomes a Nomad volume stanza
# Define host volume in Nomad client config
client {
host_volume "postgres-data" {
path = "/data/postgres"
read_only = false
}
}
In the job specification:
job "postgres-db" {
type = "service"
group "database" {
count = 1
volume "data" {
type = "host"
source = "postgres-data"
read_only = false
}
task "postgres" {
driver = "docker"
config {
image = "postgres:16"
}
volume_mount {
volume = "data"
destination = "/var/lib/postgresql/data"
}
}
}
}
For CSI-backed storage (similar to Kubernetes PV/PVC):
volume "ebs-volume" {
type = "csi"
source = "ebs-pvc"
plugin_id = "aws-ebs"
access_mode = "single-node-writer"
attachment_mode = "block-device"
}
Step 8: Convert CronJobs to Nomad Periodic Jobs
Kubernetes CronJobs map directly to Nomad's periodic job type with cron syntax:
# Kubernetes CronJob equivalent
job "cleanup-task" {
type = "periodic"
periodic {
cron = "0 3 * * *" # Every day at 3 AM
prohibit_overlap = true
timezone = "UTC"
}
group "cleanup" {
task "purge-logs" {
driver = "docker"
config {
image = "busybox:latest"
command = "/bin/sh"
args = ["-c", "rm -rf /var/log/old/*"]
}
resources {
cpu = 100
memory = 128
}
}
}
}
Step 9: Implement Health Checks and Auto-Restart
Kubernetes uses liveness and readiness probes. Nomad combines these concepts into check blocks within service stanzas, plus a restart policy on the task.
task "web-server" {
driver = "docker"
config {
image = "my-web-app:latest"
}
# Restart policy (replaces liveness probe restart behavior)
restart {
attempts = 3
interval = "10m"
delay = "15s"
mode = "fail" # "fail" stops the task after exhausting attempts
}
service {
name = "web-app"
port = "http"
check {
type = "http"
path = "/health"
interval = "10s"
timeout = "2s"
# Nomad automatically restarts unhealthy tasks
# when combined with the restart policy above
}
}
}
Step 10: Migrate CI/CD Pipelines
Your existing CI/CD pipeline that deploys to Kubernetes via kubectl apply needs adjustment for Nomad. Replace kubectl commands with nomad job run:
# Example CI deployment script for Nomad
#!/bin/bash
set -e
# Render Nomad job template with environment-specific variables
envsubst < deploy/web-app.nomad.tpl > deploy/web-app.nomad
# Validate the job specification
nomad job validate deploy/web-app.nomad
# Plan the deployment (dry-run showing changes)
nomad job plan deploy/web-app.nomad
# Run the job (apply changes)
nomad job run deploy/web-app.nomad
# Verify deployment health
nomad job status web-app
For GitOps workflows, tools like HashiCorp's nomad-scheduler or community projects like Levant provide declarative job management similar to ArgoCD or Flux.
Best Practices for Nomad Migration
Start with Non-Critical Workloads
Begin your migration journey with internal tools, batch processing jobs, or development/staging environments. These workloads are lower risk and provide valuable operational experience before tackling production customer-facing services. Use Nomad's job plan command to preview changes without applying them.
Leverage Nomad's Native Drivers for Non-Container Workloads
One of Nomad's key advantages is its support for non-containerized workloads. If you have services that run as raw binaries, JVM applications, or scripts that were awkwardly containerized for Kubernetes, consider running them with Nomad's exec or java drivers instead. This reduces image build complexity and registry dependencies.
task "legacy-jar" {
driver = "java"
config {
jar_path = "local/myapp.jar"
jvm_options = ["-Xmx512m", "-Dspring.profiles.active=prod"]
}
}
Use Consul Connect for Service Mesh
If your Kubernetes deployment uses a service mesh like Istio or Linkerd, Nomad offers Consul Connect as the equivalent. Enable it with a connect block in your service stanza to get mTLS, service-to-service authorization, and traffic policy enforcement—without sidecar injection complexity.
service {
name = "api-server"
port = "api-port"
connect {
native = false # Use sidecar proxy
sidecar_service {
proxy {
upstreams {
destination_name = "database"
local_bind_port = 5432
}
}
}
}
}
Adopt Infrastructure as Code for Nomad
Use Terraform with the Nomad provider to manage cluster configuration, namespaces, and persistent job definitions. This brings consistency to your migration and ensures that Nomad job specifications are versioned alongside infrastructure changes.
# Terraform snippet for Nomad job management
resource "nomad_job" "web_app" {
jobspec = file("${path.module}/jobs/web-app.nomad")
# Automatically purge old versions
purge_on_destroy = true
}
Monitor and Alert Differently
Kubernetes monitoring heavily relies on Prometheus and kube-state-metrics. For Nomad, use the Nomad metrics endpoint (/v1/metrics), Consul health checks, and Vault audit logs. Integrate these with your existing Prometheus/Alertmanager stack, but be aware that metrics names and structures differ. Plan time for dashboard and alert rule updates.
Test Rollback Procedures Early
Nomad's rollback mechanism differs from Kubernetes. Nomad maintains job versions automatically, and you can revert to a previous version with nomad job revert. Test this workflow in staging before production migration:
# List job versions
nomad job versions web-app
# Revert to a specific version
nomad job revert web-app 3
# Or deploy a specific previous version
nomad job run -revert-to-version 3 web-app.nomad
Handle Multi-Environment Deployments with Job Namespaces
If you use Kubernetes namespaces for environment separation (dev, staging, prod), Nomad Enterprise provides namespaces with similar isolation. In open-source Nomad, use distinct job names prefixed by environment, or run separate clusters per environment.
# Nomad Enterprise namespace example
nomad namespace apply -description "Production workloads" production
nomad job run -namespace=production web-app.nomad
# Open-source alternative: prefix job names
job "production-web-app" { ... }
job "staging-web-app" { ... }
Plan for the Learning Curve
Your team's Kubernetes expertise doesn't transfer directly. Invest in training on Nomad's job specification syntax, the difference between service/system/batch/periodic job types, Consul integration patterns, and Vault secret management workflows. Allocate time for experimentation in a sandbox environment.
Keep Kubernetes Operational During Migration
Run Nomad and Kubernetes in parallel during the transition. Use Consul for service discovery across both platforms by registering Kubernetes services in Consul via consul-k8s components. This allows gradual workload shifting without a hard cutover.
# Register a Kubernetes service in Consul for cross-platform discovery
# Using consul-k8s sync-catalog (if still running K8s)
# Nomad services can then discover K8s services via Consul DNS
consul-catalog-sync -kubeconfig=/etc/kubeconfig -consul-addr=consul:8500
Conclusion
Migrating from Kubernetes to Nomad is a strategic decision that prioritizes operational simplicity, lower resource overhead, and native multi-workload support over Kubernetes's extensive ecosystem. The migration process centers on translating Kubernetes primitives into Nomad's job specification model: Deployments become service jobs, ConfigMaps become template stanzas, Secrets move to Vault-backed templates, and service discovery shifts from kube-dns to Consul. By following a step-by-step approach—starting with cluster setup, converting workloads incrementally, handling storage and secrets properly, and updating CI/CD pipelines—teams can achieve a smooth transition. The key to success lies in running both orchestrators in parallel during migration, investing in team education on Nomad's paradigms, and embracing the HashiCorp ecosystem for secrets, service mesh, and infrastructure automation. While Nomad doesn't replicate every Kubernetes feature, its deliberate simplicity often results in fewer moving parts, easier troubleshooting, and more predictable production behavior for organizations that don't require Kubernetes's full complexity.