Internal DNS #
One of Docker’s features often treated as trivial but absolutely crucial in container architecture is internal DNS. Thanks to it, containers can find each other just by service or container name — no need to know IP addresses, no hardcoding, no extra configuration.
Without internal DNS, building multi-container systems would be far more fragile. Every time a container restarts, its IP can change. Every time you scale, new IPs appear. Without stable service discovery, one small change can bring down the entire architecture.
Docker provides internal DNS that works automatically on user-defined networks. It’s embedded in the Docker Engine, super fast, and needs no manual configuration. This article covers internal DNS in depth: how it works, when it’s active, how it interacts with Docker Compose, and how to troubleshoot when name resolution fails.
What Is Docker Internal DNS? #
Docker internal DNS is a built-in DNS server running inside the Docker Engine. When a container makes a DNS query, it’s automatically directed to this server via the 127.0.0.11 resolver.
flowchart LR
A[Container: app] -->|nslookup db| B[127.0.0.11]
B -->|Check: db container on this network?| C{Docker DNS}
C -- Yes, found --> D[Return: 172.18.0.3]
C -- No --> E[Forward to external DNS]
E --> F[Google / Cloudflare DNS]
F --> E
E --> AMain characteristics:
- Automatic resolver — containers don’t need manual
/etc/resolv.confconfiguration. - Resolves container names —
db,api,webcan be used as hostnames. - Resolves service names — in Compose,
dbrefers to thedbservice. - Round-robin — for multiple containers with the same name, returns all IPs.
- External forwarding — queries for public domains are forwarded to external DNS.
The resolver is127.0.0.11. It’s an internal IP only reachable from inside the container. When a container runscat /etc/resolv.conf, you’ll usually seenameserver 127.0.0.11. Don’t be confused —127.0.0.11is not the host’s localhost, but Docker’s embedded DNS inside the container namespace.
The Internal DNS Address: 127.0.0.11 #
Every container created with a modern network (user-defined) will have 127.0.0.11 as its resolver. How to see it:
docker run --rm --network app-net alpine cat /etc/resolv.conf
Output:
nameserver 127.0.0.11
options ndots:0
nameserver 127.0.0.11— all DNS queries are directed here.options ndots:0— the minimum number of dots before a query is treated as an FQDN.
ndots:0matters: it means Docker DNS will try to resolve short names (likedb) directly, without appending a domain suffix. That’s what makes short-name service discovery work. With a higher ndots,dbmight be treated as relative and get a domain suffix appended first.
When Is Internal DNS Active? #
Docker internal DNS only works optimally on user-defined networks. This is one of the biggest differences between the default bridge and user-defined bridges.
| Network Type | Internal DNS? | Container name resolution? |
|---|---|---|
Default bridge (bridge) | ❌ None | ❌ No (must use IP or --link) |
| User-defined bridge | ✅ Yes | ✅ Yes |
| Host network | ❌ No | ❌ No (uses the host’s resolv.conf) |
| None network | ❌ No | ❌ No |
| Overlay (Swarm) | ✅ Yes | ✅ Yes (in the Swarm scope) |
| Custom network plugins | Depends on the plugin | Depends on the plugin |
The most common trap: Developers run containers on the default bridge, then wonder whyping dbfails orcurl http://api:8080errors. This isn’t a bug — it’s by design. The default bridge has no internal DNS. The solution:docker network create my-netand run the containers on that network.
Experiment: Default vs User-Defined #
# Default bridge - DNS inactive
docker run -d --name db --network bridge postgres
docker run -it --rm --network bridge alpine sh
/ # nslookup db
# nslookup: can't resolve 'db'
# (fails)
# User-defined network - DNS active
docker network create app-net
docker run -d --name db --network app-net postgres
docker run -it --rm --network app-net alpine sh
/ # nslookup db
# Server: 127.0.0.11
# Address: 127.0.0.11:53
# Non-authoritative answer:
# Name: db
# Address: 172.18.0.2
# (works!)
The DNS Resolution Flow: Step by Step #
What happens when a container queries db:
sequenceDiagram
participant App as Application (container)
participant R as /etc/resolv.conf
participant DNS as 127.0.0.11
participant E as External DNS
App->>R: gethostbyname("db")
R->>DNS: UDP query 127.0.0.11:53
DNS->>DNS: Check: "db" container on this network?
alt Container found
DNS-->>App: Return 172.18.0.3
else Container not found
DNS->>E: Forward to 8.8.8.8
E-->>DNS: Response (or NXDOMAIN)
DNS-->>App: Forward response
endThe interesting part: as long as the container and service are on the same network, resolution succeeds with very low latency (sub-millisecond). No external DNS hop, no internet round-trip.
flowchart TB
subgraph Internal["Local Resolution (instant)"]
A[app nslookup db] --> B[127.0.0.11]
B --> C[Return container IP]
end
subgraph External["External Resolution (forward)"]
D[app nslookup google.com] --> E[127.0.0.11]
E --> F[Forward to 8.8.8.8]
F --> G[Return google IP]
endWhen does Docker DNS fail?
- Containers on different networks (no connectivity).
- Containers with the same name on different networks (ambiguous).
- Typos in the service name.
aliasesnot set correctly in Compose.links(legacy), which is deprecated — don’t use it.
Round-Robin for Multi-Container Setups #
When one service runs as multiple containers (e.g. api-1, api-2, api-3), Docker DNS can resolve all IPs at once for round-robin load balancing.
# Start 3 containers with the same name (alias)
docker network create --driver bridge scale-net
docker run -d --name api-1 --network scale-net --network-alias api my-api
docker run -d --name api-2 --network scale-net --network-alias api my-api
docker run -d --name api-3 --network scale-net --network-alias api my-api
# Query from another container
docker run -it --rm --network scale-net alpine sh
/ # nslookup api
# Name: api
# Address: 172.18.0.2
# Address: 172.18.0.3
# Address: 172.18.0.4
# Docker Compose - scale
services:
api:
image: my-api
networks:
- app-net
deploy:
replicas: 3
flowchart LR
Q[Container: client] -->|gethostbyname api| D[127.0.0.11]
D --> R1[172.18.0.2]
D --> R2[172.18.0.3]
D --> R3[172.18.0.4]
Q --> R1
Q --> R2
Q --> R3Round-robin here is DNS-level — the client receives a list of IPs and usually implements its own load balancing. This differs from load balancing at the proxy level (Nginx, HAProxy), which is more advanced.
Important: round-robin DNS isn’t perfect load balancing. Clients may cache results and not truly round-robin. For production, it’s better to use a reverse proxy (Traefik, Nginx) or an orchestrator (K8s, Swarm) with smarter load balancing.
Internal DNS in Docker Compose #
Docker Compose automatically enables internal DNS based on service names. Every service resolves automatically.
version: "3.9"
services:
web:
image: nginx
api:
image: my-api
environment:
DATABASE_URL: postgres://user:pass@db:5432/mydb
REDIS_URL: redis://cache:6379
db:
image: postgres
environment:
POSTGRES_PASSWORD: pass
cache:
image: redis
api can access db via db:5432 and cache via cache:6379 — without extra configuration. Compose automatically:
- Creates one default network for the project.
- Connects all services.
- Enables internal DNS.
Extra Aliases with aliases
#
services:
db:
image: postgres
networks:
default:
aliases:
- database
- postgres-db
- primary-db
Now db can be called: db, database, postgres-db, or primary-db. Useful for:
- Migration — old services use the name
database, new services usedb. Use aliases for compatibility. - Abstraction — application code uses
db(generic), but the underlying service can be swapped. - Multiple environments —
dev-dbin development,prod-dbin production.
External DNS Resolution #
Docker internal DNS also forwards queries for external (public) domains to upstream DNS. You can set custom DNS servers for all containers.
services:
app:
image: my-app
dns:
- 1.1.1.1
- 8.8.8.8
dns_search:
- example.com
- internal.local
Or set it at the daemon level (all containers):
// /etc/docker/daemon.json
{
"dns": ["1.1.1.1", "8.8.8.8"],
"dns-search": ["example.com"]
}
sudo systemctl restart docker
Be careful: if you set a custom DNS in the daemon and forget to restart Docker, new containers will still use the old setting. Restart the daemon every time you change daemon.json.Verifying Internal DNS #
The most effective ways to confirm internal DNS is active:
# 1. Check resolv.conf
docker exec <container> cat /etc/resolv.conf
# Must contain nameserver 127.0.0.11
# 2. Check hostname resolution
docker exec <container> nslookup <target-container>
# or
docker exec <container> getent hosts <target-container>
# 3. Test connectivity via hostname
docker exec <container> ping -c 1 <target-container>
docker exec <container> wget -qO- http://<target-service>:8080
# 4. View the container's networks
docker inspect <container> --format '{{json .NetworkSettings.Networks}}'
Troubleshooting Failed DNS #
1. “Could not resolve host” #
Common causes:
- Container on the default bridge.
- Containers on different networks.
- Typo in the name.
# Debug: check the container's networks
docker inspect <container> | grep -A 5 NetworkSettings
# Make sure they're on the same network
docker network connect app-net <container>
2. Very Slow DNS #
Common causes:
- Forwarding to a slow external DNS.
ndotssettings causing repeated queries.
# Check query time
docker exec <container> dig +stats <hostname>
# Look at "Query time"
# Set a faster DNS
docker run --dns 1.1.1.1 --dns 8.8.8.8 my-app
3. Intermittent Resolution Failures #
Common causes:
- Container restarts with a new IP.
- Overlay networks (Swarm) with quorum issues.
- DNS caching in the application (Java’s DNS cache is famously stubborn).
# Test resolution repeatedly
docker exec <container> sh -c 'for i in 1 2 3 4 5; do nslookup db; done'
DNS Caching in Applications #
Some applications and runtimes have internal DNS caches that keep query results too long. This can cause problems when containers restart with new IPs.
| Runtime | Default TTL | Notes |
|---|---|---|
| Go | 30 seconds (default) | Fast enough |
| Java | 30+ seconds (caches forever until JVM restart) | ⚠️ Common problem |
| Node.js | 0 (no cache by default) | Good |
| Python | Library-dependent | Varies |
| PHP-FPM | 60 seconds (PHP built-in) | Adequate |
Java’s DNS cache is a famous trap: the JVM stores DNS results forever (or until the TTL setting is changed). For containerized Java apps, set -Dsun.net.inetaddr.ttl=30 or use another DNS resolver library (dnsjava). Without this, a container restart can leave the app stuck on the old IP.Internal DNS Best Practices #
1. Always Use a User-Defined Network #
docker network create app-net
docker run --network app-net my-service
Without a user-defined network, internal DNS is inactive.
2. Use Service Names (Compose), Not Container Names #
# ✓ Solution: stable service name
services:
api:
image: my-api
environment:
DATABASE_HOST: db # service name, stable
# ✗ Anti-pattern: fragile container name
services:
db:
container_name: my-database # can't be scaled
image: postgres
3. Don’t Hardcode IPs #
# ✗ Anti-pattern
DATABASE_HOST=172.18.0.3 # can change
# ✓ Solution
DATABASE_HOST=db # DNS resolves
4. Set Consistent DNS #
// /etc/docker/daemon.json
{
"dns": ["1.1.1.1", "8.8.8.8"]
}
Consistent external DNS helps reproducibility.
5. Test DNS Resolution in CI/CD #
# A health check that verifies DNS
docker run --rm --network app-net my-app \
sh -c "getent hosts db && getent hosts cache"
Internal DNS in Swarm and Kubernetes #
Docker Swarm has its own, more advanced DNS service — Swarm Mode DNS. Services deployed in Swarm auto-resolve and get load-balanced.
Kubernetes has CoreDNS, running as a pod in the cluster. Service names resolve via Service objects. More advanced than Docker DNS — health checks, weighted load balancing, and service mesh integration.
flowchart TB
subgraph Docker["Docker / Compose"]
D1[127.0.0.11]
end
subgraph Swarm["Docker Swarm"]
S1[Swarm DNS<br/>tasks.service_name]
end
subgraph K8s["Kubernetes"]
K1[CoreDNS<br/>service.namespace.svc.cluster.local]
end
Docker --> Swarm --> K8sSame concept, different implementations. For development, Docker DNS is enough. For multi-host production, Swarm or K8s provide more features.
Internal DNS vs Service Discovery Tools #
| Aspect | Docker DNS | Consul | etcd | K8s CoreDNS |
|---|---|---|---|---|
| Built-in | ✅ | ❌ | ❌ | ✅ (default) |
| Multi-host | ❌ (except Swarm) | ✅ | ✅ | ✅ |
| Health checks | ❌ | ✅ | ❌ (needs a library) | ✅ |
| Load balancing | Basic round-robin | ✅ | ❌ | ✅ |
| Setup complexity | Zero | High | High | Medium |
| Best for | Single-host Compose | Traditional multi-host | Pre-era K8s multi-host | Kubernetes |
For most cases, Docker DNS is enough. It’s free, automatic, and zero-config. Only consider Consul/etcd/CoreDNS when you need health-check-based routing, multi-host, or service mesh integration.
Summary #
- Docker internal DNS is a DNS server embedded in the Docker Engine, accessed via
127.0.0.11. It resolves container and service names on user-defined networks.- Automatic resolver — containers get
127.0.0.11in/etc/resolv.confwithout manual configuration.- Only active on user-defined networks. The default bridge has NO internal DNS — this is the most common trap.
- Round-robin — multi-containers with the same alias return all IPs; clients implement load balancing.
- External queries are forwarded to upstream DNS (default: from the host). Overridable per container or at the daemon level.
- Docker Compose automatically enables service-name-based internal DNS. Every service is reachable by its service name.
- Aliases in Compose let one service have many names (for compatibility, abstraction, multiple environments).
- Anti-patterns: using the default bridge, hardcoding IPs, using
container_namein Compose, using--link(deprecated).- Troubleshooting: check
/etc/resolv.conf,nslookup,getent hosts, and make sure containers are on the same network.- Java’s DNS cache is a famous trap — set a low TTL for containerized Java apps.
- Best practices: always user-defined networks, use service names (not
container_name), don’t hardcode IPs, test DNS resolution in CI/CD.- For multi-host production, consider Swarm (built-in) or K8s (CoreDNS). Docker DNS is enough for single-host Compose.
← Previous: Port Mapping & Exposure Next: Container-to-Container Communication →