Introduction
HashiCorp Nomad is a lightweight workload orchestrator that schedules containers, VMs, binaries, and batch jobs. Unlike Kubernetes, Nomad has a simple architecture (single binary) and integrates natively with Consul for service discovery and Vault for secrets. Ansible automates the full deployment: server cluster, client agents, job submissions, ACLs, and monitoring.
Deploy Nomad Server Cluster
---
- name: Deploy Nomad servers
hosts: nomad_servers
become: true
vars:
nomad_version: "1.8.0"
nomad_datacenter: dc1
nomad_region: global
nomad_bootstrap_expect: 3
tasks:
- name: Install prerequisites
ansible.builtin.package:
name: [unzip, curl]
state: present
- name: Download Nomad
ansible.builtin.get_url:
url: "https://releases.hashicorp.com/nomad/{{ nomad_version }}/nomad_{{ nomad_version }}_linux_amd64.zip"
dest: /tmp/nomad.zip
- name: Extract Nomad
ansible.builtin.unarchive:
src: /tmp/nomad.zip
dest: /usr/local/bin/
remote_src: true
mode: '0755'
- name: Create Nomad user
ansible.builtin.user:
name: nomad
system: true
shell: /usr/sbin/nologin
- name: Create directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: nomad
mode: '0750'
loop:
- /etc/nomad.d
- /opt/nomad/data
- name: Deploy server config
ansible.builtin.template:
src: nomad-server.hcl.j2
dest: /etc/nomad.d/nomad.hcl
owner: nomad
mode: '0640'
notify: restart nomad
- name: Create systemd service
ansible.builtin.copy:
dest: /etc/systemd/system/nomad.service
content: |
[Unit]
Description=Nomad
After=network-online.target consul.service
[Service]
User=nomad
Group=nomad
ExecStart=/usr/local/bin/nomad agent -config /etc/nomad.d
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
KillSignal=SIGINT
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
mode: '0644'
notify:
- daemon reload
- restart nomad
- name: Allow Nomad through firewall
ansible.posix.firewalld:
port: "{{ item }}/tcp"
permanent: true
state: enabled
immediate: true
loop: ["4646", "4647", "4648"]
- name: Start Nomad
ansible.builtin.service:
name: nomad
state: started
enabled: true
handlers:
- name: daemon reload
ansible.builtin.systemd:
daemon_reload: true
- name: restart nomad
ansible.builtin.service:
name: nomad
state: restarted
Server Config
# templates/nomad-server.hcl.j2
datacenter = "{{ nomad_datacenter }}"
region = "{{ nomad_region }}"
data_dir = "/opt/nomad/data"
bind_addr = "0.0.0.0"
advertise {
http = "{{ ansible_default_ipv4.address }}"
rpc = "{{ ansible_default_ipv4.address }}"
serf = "{{ ansible_default_ipv4.address }}"
}
server {
enabled = true
bootstrap_expect = {{ nomad_bootstrap_expect }}
{% for server in groups['nomad_servers'] %}
{% if server != inventory_hostname %}
server_join {
retry_join = ["{{ hostvars[server].ansible_default_ipv4.address }}"]
}
{% endif %}
{% endfor %}
}
telemetry {
prometheus_metrics = true
publish_allocation_metrics = true
publish_node_metrics = true
}
{% if nomad_consul_enabled | default(true) %}
consul {
address = "127.0.0.1:8500"
auto_advertise = true
server_auto_join = true
client_auto_join = true
}
{% endif %}
Deploy Client Agents
---
- name: Deploy Nomad clients
hosts: nomad_clients
become: true
tasks:
- name: Install Nomad binary
# ... same download steps as server ...
- name: Deploy client config
ansible.builtin.template:
src: nomad-client.hcl.j2
dest: /etc/nomad.d/nomad.hcl
owner: nomad
mode: '0640'
notify: restart nomad
- name: Install Docker (for Docker driver)
ansible.builtin.package:
name: docker.io
state: present
- name: Add nomad user to docker group
ansible.builtin.user:
name: nomad
groups: docker
append: true
# templates/nomad-client.hcl.j2
datacenter = "{{ nomad_datacenter }}"
data_dir = "/opt/nomad/data"
bind_addr = "0.0.0.0"
client {
enabled = true
{% for server in groups['nomad_servers'] %}
servers = ["{{ hostvars[server].ansible_default_ipv4.address }}:4647"]
{% endfor %}
meta {
"node_type" = "{{ nomad_node_type | default('general') }}"
}
host_volume "data" {
path = "/opt/nomad/volumes/data"
read_only = false
}
}
plugin "docker" {
config {
allow_privileged = false
volumes {
enabled = true
}
}
}
Submit Jobs
- name: Submit Nomad job
ansible.builtin.uri:
url: "http://{{ groups['nomad_servers'][0] }}:4646/v1/jobs"
method: POST
body_format: json
body:
Job:
ID: webapp
Type: service
Datacenters: ["{{ nomad_datacenter }}"]
TaskGroups:
- Name: web
Count: 3
Tasks:
- Name: nginx
Driver: docker
Config:
image: "nginx:latest"
ports: ["http"]
Resources:
CPU: 500
MemoryMB: 256
Networks:
- Port:
- Label: http
To: 80
Services:
- Name: webapp
PortLabel: http
Provider: consul
Checks:
- Type: http
Path: /
Interval: 10000000000
Timeout: 2000000000
status_code: 200
delegate_to: localhost
Job from HCL File
- name: Deploy job from file
ansible.builtin.command: nomad job run /path/to/job.nomad
environment:
NOMAD_ADDR: "http://{{ groups['nomad_servers'][0] }}:4646"
changed_when: true
ACL Bootstrap
- name: Bootstrap ACL system
ansible.builtin.command: nomad acl bootstrap
register: acl_bootstrap
environment:
NOMAD_ADDR: "http://localhost:4646"
run_once: true
changed_when: "'Secret' in acl_bootstrap.stdout"
failed_when: false
Health Check
- name: Check Nomad leader
ansible.builtin.uri:
url: "http://localhost:4646/v1/status/leader"
return_content: true
register: leader
- name: List cluster members
ansible.builtin.command: nomad server members
register: members
changed_when: false
- name: Check node status
ansible.builtin.command: nomad node status
register: nodes
changed_when: false
- name: Check job status
ansible.builtin.command: nomad job status
register: jobs
changed_when: false
Troubleshooting
Allocation Failures
- name: Check allocation status
ansible.builtin.command: "nomad alloc status {{ alloc_id }}"
register: alloc_status
changed_when: false
environment:
NOMAD_ADDR: "http://localhost:4646"
Client Not Joining
- name: Check client connectivity to servers
ansible.builtin.wait_for:
host: "{{ hostvars[item].ansible_default_ipv4.address }}"
port: 4647
timeout: 5
loop: "{{ groups['nomad_servers'] }}"
Related Articles
Conclusion
Nomad is the simpler alternative to Kubernetes — a single binary that schedules containers, VMs, and raw binaries. Ansible deploys the server cluster and client agents, configures Consul integration for service discovery, and submits jobs via API or CLI. Use Nomad when you need orchestration without Kubernetes complexity: smaller teams, mixed workloads (containers + legacy), and multi-datacenter federation.