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
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
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
⚠️ You can replace the
shell
tasks withansible.builtin.get_url
andapt_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
}
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
}
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"
}
}
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]
Running the Playbook
Split your deployment by tags:
ansible-playbook consul.yml --tags master -v
ansible-playbook consul.yml --tags client -v
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.
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.