Consul â Service Discovery & Service Mesh Guide
In this tutorial, you'll learn about Consul. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
HashiCorp Consul is a service networking solution that provides service discovery, health checking, KV storage, and a secure service mesh (Connect) across any runtime platform and cloud.
What You'll Learn
Why It Matters
In dynamic environments with auto-scaling and container Orchestration, IP addresses change constantly. Applications cannot use hardcoded IPs. Consul provides a DNS-based service registry where services register themselves and clients discover them by name. DodaTech uses Consul for service discovery across 300+ Microservices, enabling seamless communication regardless of underlying infrastructure changes.
Real-World Use
DodaZIP's API Gateway discovers upstream services via Consul DNS. When a new instance of the user-service starts, it registers with Consul, and the gateway automatically routes traffic to it. When an instance fails health checks, Consul removes it from service â without any configuration changes.
flowchart TD
A[Service A] --> B[Consul Agent]
C[Service B] --> D[Consul Agent]
E[API Gateway] --> F[Consul DNS :8600]
F --> G["user-service.service.consul"]
G --> B
G --> D
B --> H[Health Check: TCP 8080]
D --> I[Health Check: HTTP /health]
H --> J{Passing?}
J -->|YES| K[Registered]
J -->|NO| L[Deregistered]
style B fill:#E03875,color:#fff
style D fill:#E03875,color:#fff
Installation
# Install Consul
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install consul
# Start Consul in dev mode
consul agent -dev -ui
# Expected output:
# ==> Starting Consul agent...
# ==> Consul agent running!
# Version: '1.19.0'
# Node name: 'consul-server'
# Datacenter: 'dc1'
# Server: true (bootstrap: true)
# Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
# Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
# Verify
consul members
# Expected output:
# Node Address Status Type Build Protocol DC
# consul-server 127.0.0.1:8301 alive server 1.19.0 2 dc1
Server Configuration
# /etc/consul.d/consul.hcl
datacenter = "dc1"
data_dir = "/opt/consul"
server = true
bootstrap_expect = 3
ui_config {
enabled = true
}
client_addr = "0.0.0.0"
bind_addr = "{{ GetInterfaceIP \"eth0\" }}"
advertise_addr = "{{ GetInterfaceIP \"eth0\" }}"
retry_join = ["consul-01.dodatech.com", "consul-02.dodatech.com", "consul-03.dodatech.com"]
encrypt = "aPu1gZxV7VJqVfL5kH4wYQ=="
connect {
enabled = true
}
ports {
grpc = 8502
}
telemetry {
prometheus_retention_time = "30s"
}
acl = {
enabled = true
default_policy = "deny"
enable_token_persistence = true
}
Service Registration
# /etc/consul.d/user-service.hcl
service {
name = "user-service"
id = "user-service-01"
port = 8080
tags = ["api", "production", "v2"]
meta = {
version = "2.5.0"
commit_sha = "a1b2c3d"
owner = "platform-team"
}
connect {
sidecar_service {
port = 20000
proxy {
upstreams {
destination_name = "postgres"
local_bind_port = 5432
}
upstreams {
destination_name = "redis"
local_bind_port = 6379
}
}
}
}
check {
id = "user-service-http"
name = "HTTP Health Check"
http = "http://localhost:8080/health"
method = "GET"
interval = "10s"
timeout = "2s"
success_threshold = 1
failure_threshold = 3
deregister_critical_service_after = "5m"
}
check {
id = "user-service-ttl"
name = "TTL Check"
ttl = "30s"
notes = "Application must report health via API"
deregister_critical_service_after = "10m"
}
}
Service Discovery
# DNS-based discovery
dig @127.0.0.1 -p 8600 user-service.service.consul
# Expected output:
# ; <<>> DiG 9.18 <<>> user-service.service.consul
# ;; ANSWER SECTION:
# user-service.service.consul. 0 IN A 10.0.1.50
# user-service.service.consul. 0 IN A 10.0.1.51
# user-service.service.consul. 0 IN A 10.0.1.52
# HTTP API discovery
curl http://localhost:8500/v1/catalog/service/user-service
# Expected output:
# [
# {
# "ID": "a1b2c3d4-...", "# "Node": "web-01"", "# "Address": "10.0.1.50"", "# "ServiceID": "user-service-01"", "# "ServiceName": "user-service"", "# "ServicePort": 8080", "# "ServiceTags": ["api"", "production", "v2"]
# }
# ]
# Health filtering (only passing instances)
curl http://localhost:8500/v1/health/service/user-service?passing
# Register service via API
curl -X PUT -d '{
"Name": "cache-service",
"Port": 6379,
"Check": {
"TCP": "localhost:6379",
"Interval": "10s"
}
}' http://localhost:8500/v1/agent/service/register
KV Store
# Write a key
consul kv put config/dodazip/api_url https://api.dodatech.com
# Expected output:
# Success! Data written to: config/dodazip/api_url
# Write multiple keys in a hierarchy
consul kv put config/dodazip/database/host postgres.dodatech.com
consul kv put config/dodazip/database/port 5432
consul kv put config/dodazip/database/name dodazip
# Get a key
consul kv get config/dodazip/api_url
# Expected output:
# https://api.dodatech.com
# Get all keys with prefix
consul kv get -keys config/dodazip/
# Expected output:
# config/dodazip/api_url
# config/dodazip/database/host
# config/dodazip/database/port
# config/dodazip/database/name
# Delete a key
consul kv delete config/dodazip/api_url
Consul-Template
# /etc/consul-template.d/config.hcl
template {
source = "/etc/consul-template/templates/nginx.conf.ctmpl"
destination = "/etc/nginx/conf.d/upstream.conf"
command = "nginx -s reload"
perms = 0644
}
template {
source = "/etc/consul-template/templates/prometheus.yml.ctmpl"
destination = "/etc/prometheus/prometheus.yml"
command = "systemctl reload prometheus"
perms = 0644
}
# /etc/consul-template/templates/nginx.conf.ctmpl
upstream user-service {
least_conn;
{{ range service "user-service.passing" }}
server {{ .Address }}:{{ .Port }};
{{ end }}
}
upstream api-gateway {
{{ range service "api-gateway.passing" }}
server {{ .Address }}:{{ .Port }} max_fails=3 fail_timeout=30s;
{{ end }}
}
# Run consul-template
consul-template -config /etc/consul-template.d/config.hcl
# Expected output:
# 2026/06/24 10:00:00 [INFO] consul-template v0.37.0
# 2026/06/24 10:00:00 [INFO] Quiescence: min=10s, max=20s
# 2026/06/24 10:00:01 [INFO] rendered /etc/nginx/conf.d/upstream.conf
Service Mesh with Connect
# Intentions for service-to-service authorization
# /etc/consul.d/intentions.hcl
Kind = "service-intentions"
List = [
{
Name = "api-gateway]
Sources = [
{
Name = "user-service]
Action = "allow"
},
{
Name = "auth-service"
Action = "allow"
}
]
},
{
Name = "postgres"
Sources = [
{
Name = "user-service]
Action = "allow"
},
{
Name = "auth-service"
Action = "allow"
}
]
}
]
# Create intention via CLI
consul intention create api-gateway user-service
# Expected output:
# Created: api-gateway => user-service (allow)
# Verify intentions
consul intention check api-gateway user-service
# Expected output:
# api-gateway can access user-service (allowed)
Common Configuration Mistakes
Not setting
deregister_critical_service_after: Without this, failed services remain in the catalog indefinitely as critical, cluttering the service list. Set to 5-10 minutes for auto-cleanup.Running servers without
Bootstrap_expect: Without this, servers cannot form a quorum and the cluster remains unstable. Set to the expected server count (3 or 5).Mixing LAN and WAN gossip encryption keys: Different datacenters need different gossip keys but must share the same Replication token. Keep keys organized in Vault or a secrets manager.
Forgetting to set up ACLs: Without ACLs, any client can register or deregister any service. Enable ACLs with
default_policy = "deny"and generate tokens per service.Not using health check passing filters: Discovered services may include unhealthy instances. Always use
/v1/health/service/name?passingor the.passingtemplate filter.
Practice Questions
What is the difference between Consul and etcd? Answer: Consul provides service discovery, health checking, DNS interface, KV store, and service mesh. etcd is a distributed key-value store without discovery or health checking.
How does Consul perform health checks? Answer: Consul supports script, HTTP, TCP, TTL, Docker, and gRPC health checks. Checks run on the Consul client agent, which reports status to the server cluster.
What is Consul Connect? Answer: Connect provides automatic mTLS between services using sidecar proxies. It encrypts all service-to-service traffic and enforces access control via intentions.
How does Consul-Template work? Answer: Consul-Template watches Consul for changes to services or KV keys. When changes occur, it renders templates (using Go template syntax) and optionally runs a command.
Challenge
Deploy a production Consul cluster: set up a 3-server cluster with Raft consensus, configure ACLs with default deny, register a user-service with HTTP health check and TTL health check, register a postgres service with TCP check, set up Consul-Template to generate NGINX upstream configuration dynamically, configure service mesh with Connect sidecar proxies, create intentions allowing only api-gateway to call user-service, and set up Consul DNS forwarding for Kubernetes pods.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro