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
| Rule | Detail |
|---|---|
| DNS works on user-defined networks | The default bridge network does not have DNS. Always create your own |
| Names are network-scoped | A container can only resolve names of containers on the same network |
Names come from --name or Compose service names | In Compose, the service key (db, api) becomes the DNS hostname |
| IPs can change | Container 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
| Error | Likely Cause | Fix |
|---|---|---|
Name not found / NXDOMAIN | Containers are on different networks | Connect both to the same user-defined network |
Connection refused | DNS resolves, but nothing is listening on that port | Check target container is running and listening |
Timeout | Firewall or network policy blocking traffic | Check firewall rules and network configuration |
| Intermittent failures | Startup race (app queries DNS before target is ready) | Add retry logic or depends_on with health checks |
Using localhost between containers | localhost refers to the container itself, not other services | Use 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
- Continue to Networking with Compose to design full network topologies for multi-service stacks.