How does service discovery work in Consul and what are the DNS and HTTP API interfaces?
Quick Answer
Consul service discovery works by having services register themselves with the local Consul agent, which replicates registration data across the cluster. Other services discover registered endpoints using either the Consul DNS interface (querying service-name.service.consul) or the HTTP API (/v1/health/service/), with both methods returning only healthy instances by default.
Detailed Answer
Service discovery in Consul follows a register-then-query pattern that eliminates the need for hardcoded service endpoints. When a service like payments-api starts up, it registers itself with the local Consul client agent by providing its service name, IP address, port number, and health check definition. The client agent forwards this registration to the Consul server cluster, which stores it in the replicated service catalog. From that moment on, any other service in the network can discover payments-api by querying Consul through one of its two discovery interfaces: DNS or HTTP API.
The DNS interface is the simplest way to discover services and requires zero application code changes. Consul runs a built-in DNS server on port 8600 that responds to queries in the format <service-name>.service.consul. When order-service wants to find payments-api, it queries payments-api.service.consul and receives a DNS response containing the IP addresses of all healthy instances. Consul supports both A records (returning IP addresses) and SRV records (returning IP addresses with port numbers). By configuring the system DNS resolver to forward the .consul domain to Consul's DNS server, applications can use standard hostname resolution without any Consul-specific client libraries. This approach works seamlessly with any programming language and framework.
The HTTP API provides richer functionality for applications that need more detailed service information. The primary discovery endpoint is /v1/health/service/<service-name>, which returns a JSON payload containing detailed information about each service instance, including its IP address, port, metadata tags, health check status, and the node it is running on. Applications can filter results by tags (for example, querying only instances tagged with version:v2), datacenter, or health status. The HTTP API also supports blocking queries, a long-polling mechanism where the client specifies an index value and the server holds the connection open until the service catalog changes, enabling near-real-time updates without continuous polling.
Consul also supports prepared queries, which are stored query definitions that add a layer of abstraction over basic service discovery. Prepared queries can implement failover logic, such as querying services in the local datacenter first and falling back to a remote datacenter if none are available. They also support near-sorting, which orders results by estimated network round-trip time from the querying node, enabling latency-aware load balancing. This is particularly valuable in multi-datacenter deployments where user-auth-service might exist in both us-east-1 and eu-west-1, and you want requests to prefer the geographically closer instances.
A common mistake that beginners make is not understanding the difference between catalog endpoints and health endpoints. The catalog endpoint /v1/catalog/service/ returns all registered instances regardless of health status, while the health endpoint /v1/health/service/ includes health check information and can filter out unhealthy instances with the passing query parameter. In production, you should almost always use the health endpoint to avoid routing traffic to failing instances. Another frequent pitfall is not configuring DNS TTL values appropriately. If DNS clients cache Consul responses for too long, they will continue sending traffic to instances that have been deregistered or marked unhealthy, defeating the purpose of dynamic service discovery.