consul
HashiCorp Consul for service discovery, health checking, and service mesh. Use when the user needs to register services, perform health checks, store configuration in the KV store, or set up Connect f
SKILL.mdskills/consul/SKILL.md
GitHubConsul
Overview
Consul provides service discovery, health checking, KV storage, and service mesh capabilities. The current release line is 2.x (2.0.4 as of September 2026); the commands below work the same on 1.x. Since 2023 Consul is under the Business Source License, not an open-source one: check the terms before offering it as a hosted product. Never run a production agent with -dev, which keeps everything in memory with no ACLs or TLS.
Instructions
Installation
# Install Consul
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /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
# macOS alternative: brew tap hashicorp/tap && brew install hashicorp/tap/consul
# Start dev agent
consul agent -dev
# Check cluster members
consul members
Server Configuration
# consul-server.hcl — Production Consul server config
datacenter = "dc1"
data_dir = "/opt/consul/data"
log_level = "INFO"
server = true
bootstrap_expect = 3
bind_addr = "{{ GetPrivateInterfaces | include \"network\" \"10.0.0.0/8\" | attr \"address\" }}"
client_addr = "0.0.0.0"
ui_config { enabled = true }
connect { enabled = true }
ports {
grpc = 8502 # plaintext xDS for Envoy on the same host; disabled (-1) by default
grpc_tls = 8503 # TLS xDS for proxies on other hosts
}
# generate with: consul keygen
encrypt = "pUqJrVyVRj5jsiYEkM/tFQYfWyJIv4s3XkvDwy7Cu5s="
acl {
enabled = true
default_policy = "deny"
enable_token_persistence = true
tokens {
# agent token: needs node "<name>" write + service_prefix "" read
agent = "0b4f3c1e-7a52-4d0e-9c1b-5d2f8e6a1b34"
# default token for DNS and unauthenticated requests: read-only
default = "6e1d9a20-3c4b-4f77-8a0e-2b9c7d51f0aa"
}
}
tls {
defaults {
ca_file = "/opt/consul/tls/ca.pem"
cert_file = "/opt/consul/tls/server.pem"
key_file = "/opt/consul/tls/server-key.pem"
verify_incoming = true
verify_outgoing = true
}
internal_rpc {
verify_server_hostname = true
}
}
autopilot {
cleanup_dead_servers = true
last_contact_threshold = "200ms"
server_stabilization_time = "10s"
}
Service Registration
// services/web.json — Register web service with health check
{
"service": {
"name": "web",
"port": 8080,
"tags": ["production", "v2"],
"meta": {
"version": "2.0.0"
},
"check": {
"http": "http://localhost:8080/health",
"interval": "10s",
"timeout": "5s",
"deregister_critical_service_after": "30m"
}
}
}
consul services register services/web.json
consul services deregister -id=web
dig @127.0.0.1 -p 8600 web.service.consul SRV
curl http://localhost:8500/v1/health/service/web?passing=true
consul catalog services
consul catalog nodes
KV Store
# Key-value operations
consul kv put config/app/db_host "orders-db.internal.shopfront.io"
consul kv put config/app/db_port "5432"
consul kv get config/app/db_host
consul kv get -recurse config/app/
consul kv export config/ > backup.json
consul kv import @backup.json
consul kv delete config/app/db_host
consul kv delete -recurse config/app/
consul watch -type=key -key=config/app/db_host /scripts/reload.sh
Service Mesh (Connect)
# connect-proxy.hcl — Service with sidecar proxy
service {
name = "web"
port = 8080
connect {
sidecar_service {
proxy {
upstreams {
destination_name = "api"
local_bind_port = 9091
}
upstreams {
destination_name = "database"
local_bind_port = 9092
}
}
}
}
}
Register the sidecar, then start Envoy next to the service (Envoy must be installed; the agent needs the gRPC port from the config above):
consul services register connect-proxy.hcl
consul connect envoy -sidecar-for web -token-file=/etc/consul.d/web.token
# intentions.hcl — Service intentions for access control
Kind = "service-intentions"
Name = "api"
Sources = [
{
Name = "web"
Action = "allow"
},
{
Name = "*"
Action = "deny"
}
]
# Manage intentions
consul config write intentions.hcl
consul config read -kind service-intentions -name api
consul intention check web api
The consul intention subcommands (create, list, ...) are deprecated since 1.9 in favour of the service-intentions config entry shown above; intention check is still the quick way to test a pair.
Prepared Queries
# Create prepared query for failover across datacenters
curl -X POST http://localhost:8500/v1/query \
-H "X-Consul-Token: $CONSUL_HTTP_TOKEN" -d '{
"Name": "web-query",
"Service": {
"Service": "web",
"Tags": ["production"],
"Failover": {
"Datacenters": ["dc2", "dc3"]
},
"OnlyPassing": true
}
}'
ACL Configuration
# Bootstrap ACL system (once; save the SecretID, e.g. export CONSUL_HTTP_TOKEN)
consul acl bootstrap
# Create policy
consul acl policy create -name "app-read" \
-rules='service_prefix "web" { policy = "read" } key_prefix "config/app" { policy = "read" }'
# Create token with policy
consul acl token create -description "App token" -policy-name "app-read"
Common Commands
# Cluster management
consul join 10.0.1.10
consul leave
consul operator raft list-peers
consul operator autopilot state
# Snapshots for backup
consul snapshot save backup.snap
consul snapshot restore backup.snap
consul monitor -log-level=debug
Examples
Example 1: Register a web service and find it
User request: "Register my storefront API on port 8080 with a health check and look it up through DNS."
consul services register services/web.json
dig @127.0.0.1 -p 8600 web.service.consul SRV
curl -s "http://localhost:8500/v1/health/service/web?passing=true"
The SRV answer lists the node and port 8080 only while the /health check passes; a failing check removes the instance from the answer.
Example 2: Lock down service-to-service traffic
User request: "Only the web service may call api in our mesh."
consul config write intentions.hcl
consul intention check web api # Allowed
consul intention check cart api # Denied
Guidelines
- With
acl.default_policy = "deny", setacl.tokens.agentandacl.tokens.default; otherwise registration, anti-entropy and DNS fail with permission errors. Pass tokens throughCONSUL_HTTP_TOKENor-token-file, never inline in shell history. - Do not bind
client_addrto0.0.0.0without ACLs and TLS: the HTTP API then controls the cluster. - Run 3 or 5 servers (
bootstrap_expect), never an even count. Takeconsul snapshot savebackups on a schedule and test restores. - Back up gossip keys and rotate them with
consul keyring. - Use
deregister_critical_service_afterso dead instances do not linger. - For simple discovery without a mesh, Consul can be overkill; use DNS or the platform's own discovery.