Skip to main content

DNS and Service Discovery

Docker has a built-in DNS server that lets containers on the same user-defined network find each other by name. Instead of hardcoding IP addresses (which change every time a container restarts), you use service names like db, api, or cache.

How Docker DNS Works

flowchart LR
subgraph net["User-Defined Bridge Network"]
A["api container"] -->|"DNS: db"| DNS["Docker<br/>DNS Server"]
DNS -->|"172.18.0.3"| A
B["db container<br/>(172.18.0.3)"]
end

style DNS fill:#e3f2fd,stroke:#1565c0

When the api container connects to hostname db, Docker's embedded DNS resolves it to the IP address of the db container on the same network.

Key Rules

RuleDetail
DNS works on user-defined networksThe default bridge network does not have DNS. Always create your own
Names are network-scopedA container can only resolve names of containers on the same network
Names come from --name or Compose service namesIn Compose, the service key (db, api) becomes the DNS hostname
IPs can changeContainer IPs change on recreation. Never hardcode IPs -- use names

Basic Example

# Create a network
docker network create app-net

# Start a database
docker run -d --name db --network app-net postgres:16

# Test DNS resolution from another container
docker run --rm --network app-net alpine:3.20 nslookup db
# Output: Name: db Address: 172.18.0.2

DNS in Docker Compose

In Compose, service names automatically become DNS hostnames on shared networks:

services:
api:
image: my-api:1.0.0
environment:
DB_HOST: db # Use the service name, not an IP
REDIS_HOST: cache
networks: [back]

db:
image: postgres:16
networks: [back]

cache:
image: redis:7-alpine
networks: [back]

networks:
back: {}

The api container can reach db on port 5432 and cache on port 6379 using their service names.

Network Segmentation and DNS Scope

DNS only works within a shared network. Use this to control which services can see each other:

flowchart TD
subgraph front["front network"]
Web["web"]
end
subgraph back["back network"]
API["api"]
DB["db"]
end

Web <-->|"Can reach api"| API
API <-->|"Can reach db"| DB
Web x--x|"Cannot reach db"| DB

style front fill:#e3f2fd,stroke:#1565c0
style back fill:#e8f5e9,stroke:#2e7d32

In this setup, web is on both front and back, so it can reach api. But web cannot reach db directly because they do not share a network (unless web is also on back).

Debugging DNS Issues

Most Docker networking problems are actually DNS/discovery problems. Follow this order:

Step 1: Verify Network Membership

# Check what networks a container is on
docker inspect -f '{{json .NetworkSettings.Networks}}' api

# Check which containers are on a network
docker network inspect app-net

If two containers are not on the same network, DNS cannot resolve between them. This is the #1 root cause.

Step 2: Test Name Resolution

# From inside the calling container
docker exec -it api sh
nslookup db
# or
getent hosts db

Step 3: Test Connectivity

# If DNS resolves but connection fails, check if the port is open
docker exec -it api sh
nc -vz db 5432

Quick Diagnostic with a Temp Container

docker run --rm -it --network app-net alpine:3.20 sh
# Inside: nslookup db && nc -vz db 5432

This avoids modifying your running containers.

Common DNS Errors and Fixes

ErrorLikely CauseFix
Name not found / NXDOMAINContainers are on different networksConnect both to the same user-defined network
Connection refusedDNS resolves, but nothing is listening on that portCheck target container is running and listening
TimeoutFirewall or network policy blocking trafficCheck firewall rules and network configuration
Intermittent failuresStartup race (app queries DNS before target is ready)Add retry logic or depends_on with health checks
Using localhost between containerslocalhost refers to the container itself, not other servicesUse the service name instead (db, api)

Key Takeaways

  • Docker DNS works on user-defined networks only. Always create your own network.
  • Use service names (container name or Compose service key) instead of IP addresses in application config.
  • DNS is network-scoped -- containers must share a network to resolve each other.
  • When debugging connectivity, check network membership first, then DNS, then port listening.
  • Use temporary debug containers (docker run --rm -it --network ...) to test without touching production.

What's Next