DEV Community

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

Posted on

Automating Consul with Ansible: Infrastructure DNS for Devs

Hi there! I'm Maneshwar. Right now, I’m building LiveAPI, a first-of-its-kind tool that helps you automatically index API endpoints across all your repositories. LiveAPI makes it easier to discover, understand, and interact with APIs in large infrastructures.

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.

🧠 Bonus: Let LiveAPI Handle Your APIs

LiveAPI helps you get all your backend APIs documented in a few minutes.

With LiveAPI, you can generate interactive API docs that allow users to search and execute endpoints directly from the browser.

LiveAPI Demo

If you're tired of updating Swagger manually or syncing Postman collections, give it a shot.

Top comments (0)

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