DEV Community

Cover image for Automating Consul with Ansible: Infrastructure DNS for Devs
Athreya aka Maneshwar
Athreya aka Maneshwar

Posted on • Edited on

Automating Consul with Ansible: Infrastructure DNS for Devs

Hello, I'm Maneshwar. I'm working on FreeDevTools online currently building **one place for all dev tools, cheat codes, and TLDRs* — a free, open-source hub where developers can quickly find and use tools without any hassle of searching all over the internet.

Continuing our Ansible journey, let’s wire up Consul
— HashiCorp’s service mesh and internal DNS provider — using clean Ansible roles.

We’ll install it using HashiCorp’s apt repository, configure it in a role-driven fashion, and deploy Consul agents as either servers or clients using tags. Let's get to it.

The Playbook

Your consul.yml playbook defines which hosts should run the role and how we want to tag their responsibilities:

- name: Install and configure Consul
  hosts: all
  become: yes
  roles:
    - consul
  tags:
    - master
    - client
Enter fullscreen mode Exit fullscreen mode

This gives us the flexibility to run only server or client setup by tagging the playbook execution later.

🗂 Folder Layout

Here’s how the consul role is structured:

ansible-galaxy init roles/consul --offline

roles/consul/
├── defaults/main.yml
├── handlers/main.yml
├── tasks/
│   ├── install.yml
│   ├── configure.yml
│   └── main.yml
├── templates/
│   ├── client_consul.hcl.j2
│   ├── master_consul.hcl.j2
│   └── master_server.hcl.j2
├── vars/main.yml
Enter fullscreen mode Exit fullscreen mode

Installing Consul the Right Way

Use the official HashiCorp GPG key and apt repo setup (the new secure way):

tasks/install.yml

- name: Add HashiCorp GPG key (new method)
  ansible.builtin.shell: |
    curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /etc/apt/keyrings/hashicorp-archive-keyring.gpg > /dev/null
  args:
    creates: /etc/apt/keyrings/hashicorp-archive-keyring.gpg

- name: Add HashiCorp repository (new method)
  ansible.builtin.shell: |
    echo "deb [signed-by=/etc/apt/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
  args:
    creates: /etc/apt/sources.list.d/hashicorp.list

- name: Run apt update
  ansible.builtin.shell: apt update

- name: Install Consul
  ansible.builtin.apt:
    name: consul
    state: present
    update_cache: no
Enter fullscreen mode Exit fullscreen mode

⚠️ You can replace the shell tasks with ansible.builtin.get_url and apt_repository for a cleaner, idempotent install — but this approach is closer to the manual HashiCorp documentation and works well for quick setups.

Templates: Server & Client Configs

Define separate configuration templates for master/server and clients.

templates/master_consul.hcl.j2

node_name = "consul-{{ inventory_hostname }}-server"
server    = true
bootstrap = true
datacenter = "dc1"
data_dir   = "consul/data"
log_level  = "INFO"
bind_addr = "0.0.0.0"
client_addr = "0.0.0.0"
advertise_addr = "{{ internal_ip }}"
ui_config {
  enabled = true
}
connect {
  enabled = true
}
dns_config {
  enable_truncate = true
}
Enter fullscreen mode Exit fullscreen mode

Use this for Consul servers that need to bootstrap the cluster and expose the UI.

templates/master_server.hcl.j2

log_level = "DEBUG"
server = true
bootstrap_expect = 1
advertise_addr = "{{ internal_ip }}"
connect {
  enabled = true
}
ui_config {
  enabled = true
}
Enter fullscreen mode Exit fullscreen mode

This can act as an additional config layer — or be merged into the main server template if needed.

templates/client_consul.hcl.j2

node_name  = "consul-{{ inventory_hostname }}-client"
server     = false
datacenter = "dc1"
data_dir   = "/opt/consul"
log_level  = "INFO"
bind_addr      = "{{ internal_ip }}"
advertise_addr = "{{ internal_ip }}"
retry_join = ["{{ groups['nomadclientandservers'] | map('extract', hostvars, 'internal_ip') | list | first }}"]
dns_config {
  enable_truncate = true
}
service {
  id      = "dns"
  name    = "dns"
  tags    = ["primary"]
  address = "localhost"
  port    = 8600
  check {
    id       = "dns"
    name     = "Consul DNS TCP on port 8600"
    tcp      = "localhost:8600"
    interval = "10s"
    timeout  = "1s"
  }
}
Enter fullscreen mode Exit fullscreen mode

This config sets up DNS forwarding and a basic health check for clients. Ensure internal_ip is defined in your host vars.

Configuring Consul with Ansible

Now, wire it all together in the configure.yml:

tasks/configure.yml

- name: Create consul data directory
  ansible.builtin.file:
    path: /opt/consul
    state: directory
    owner: consul
    group: consul
    mode: 0755
  tags: [master, client]

- name: Set proper permissions for consul directories
  ansible.builtin.file:
    path: "{{ item }}"
    state: directory
    owner: consul
    group: consul
    recurse: yes
  loop:
    - /opt/consul
    - /etc/consul.d
  tags: [master, client]

- name: Deploy master Consul config
  ansible.builtin.template:
    src: master_consul.hcl.j2
    dest: /etc/consul.d/consul.hcl
    owner: consul
    group: consul
    mode: 0644
  when: "'master' in ansible_run_tags"
  tags: master

- name: Deploy master server config
  ansible.builtin.template:
    src: master_server.hcl.j2
    dest: /etc/consul.d/server.hcl
    owner: consul
    group: consul
    mode: 0644
  when: "'master' in ansible_run_tags"
  tags: master

- name: Deploy client Consul config
  ansible.builtin.template:
    src: client_consul.hcl.j2
    dest: /etc/consul.d/consul.hcl
    owner: consul
    group: consul
    mode: 0644
  when: "'client' in ansible_run_tags"
  tags: client

- name: Restart consul service
  ansible.builtin.systemd:
    name: consul
    state: restarted
  tags: [master, client]
Enter fullscreen mode Exit fullscreen mode

Running the Playbook

Split your deployment by tags:

ansible-playbook consul.yml --tags master -v
ansible-playbook consul.yml --tags client -v
Enter fullscreen mode Exit fullscreen mode

This gives you full control over when to deploy servers vs clients.

What You Achieved

  • Installed Consul using HashiCorp’s secure apt setup
  • Bootstrapped a Consul server with the UI enabled
  • Deployed DNS-aware clients with health checks
  • Used tags to keep roles clean and reusable

This approach brings modularity, repeatability, and clean separation to your infra automation.

FreeDevTools

I’ve been building FreeDevTools.

A collection of UI/UX-focused tools crafted to simplify workflows, save time, and reduce friction in searching tools/materials.

Any feedback or contributors are welcome!

It’s online, open-source, and ready for anyone to use.

👉 Check it out: FreeDevTools
⭐ Star it on GitHub: freedevtools

Let’s make it even better together.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.