Ops Notes

Docker Container DNS Resolution Fails on Same Host? A Complete Troubleshooting Guide from Root Cause to Fix

In-depth analysis of why Docker containers on the same host can't resolve each other by container name, covering embedded DNS mechanics, user-defined bridge networks, and real-world failure cases from Reddit and Hacker News.

Cloud & DevOps Visualization

Key Takeaways

  • 90% of Docker inter-container DNS failures happen because both containers aren’t attached to the same user-defined bridge network.
  • The default docker0 bridge network only supports IP-based communication, not automatic container name DNS resolution.
  • docker run --link is a deprecated hack you should stop using; the correct approach is docker network create + --network assignment.
  • Community posts on Reddit r/homelab show most failures stem from mixing host network mode or forgetting custom network gateway settings.
  • One simple command — docker exec -it <container> cat /etc/resolv.conf — tells you immediately if the DNS config has gone sideways.

1. How This Problem Shows Up: A Real-World Failure

Friday night. I’m doom-scrolling Reddit when I hit a post on r/homelab: “Issue accessing two containers via DNS on same docker.” The comment section was a dumpster fire — people arguing about --link, others screaming “use docker-compose,” and that one guy who always says “just use Kubernetes.”

My blood pressure spiked. I’ve seen this exact failure pattern too many times. Three months ago on a three-node Docker Swarm cluster, I had AdGuardHome and Portainer running on the same Ubuntu VM. AdGuardHome could ping Portainer’s IP just fine, but portainer as a hostname? Dead. Took us three hours to realize they were on different networks — one on bridge, the other on host.

You think that’s the only trap? Keep reading.


2. How Docker Container DNS Actually Works

2.1 The Embedded DNS Server

Since Docker 1.10, every Docker daemon runs an embedded DNS server listening on 127.0.0.11:53. But here’s the kicker — it doesn’t activate for every container.

The cardinal rule: The container must be attached to a user-defined network.

When a container connects to a custom network, Docker rewrites its /etc/resolv.conf to point nameserver to 127.0.0.11. Docker’s resolver handles all .local domain queries. Container names get registered as A records, and other containers on the same network can resolve them by name.

2.2 Default Bridge vs. User-Defined Bridge: The Fundamental Difference

I’m pasting this table because I’m tired of explaining it to people who say “but both containers are on the same bridge”:

FeatureDefault Bridge (docker0)User-Defined Bridge
Auto DNS by container name❌ Not supported✅ Supported
Communication via IP✅ Supported✅ Supported
--link manual aliasing✅ Supported (deprecated)❌ Not needed
Network isolation❌ All containers can talk✅ Configurable
DNS injection❌ Uses host DNS✅ Uses embedded DNS (127.0.0.11)
Hot-plug containers❌ Requires restart✅ Works at runtime

The root cause is simple: you’re not on a custom network, so the DNS resolution doesn’t exist.


3. Root Cause Analysis + Diagnostic Commands

3.1 The Debug Workflow

Here’s exactly what I run when I hit this problem:

# 1. Check which networks each container is on
docker inspect <container1> | jq '.[].NetworkSettings.Networks | keys'
docker inspect <container2> | jq '.[].NetworkSettings.Networks | keys'

# 2. Verify /etc/resolv.conf to confirm DNS target
docker exec <container1> cat /etc/resolv.conf
# Expected: nameserver 127.0.0.11
# Bad: nameserver 8.8.8.8 or 192.168.1.1 (external DNS)

# 3. Test DNS resolution directly
docker exec <container1> ping <container2>
docker exec <container1> nslookup <container2> 127.0.0.11
# If nslookup fails, it's a network-level issue

# 4. Inspect the custom network membership
docker network ls
docker network inspect <network_name>

3.2 Dissecting the r/homelab Failure

The Reddit user’s setup looked like this:

  • Pi-hole → running on a VM (not a container)
  • Nginx Reverse Proxy → LXC container
  • Docker VM containing:
    • AdGuardHome container
    • Portainer container

The problem? Both containers were started with docker run, but one used --network host to grab the host’s network stack. Once a container uses host networking, Docker never injects 127.0.0.11 into its /etc/resolv.conf. Portainer had no idea who 127.0.0.11 was, so it went to the host’s DNS servers to look up adguardhome — and found nothing.

3.3 The Hidden Asymmetry Trap

Someone on Hacker News (in the 227-point Codex exclusion issue thread) described a nastier variant: two services started via docker-compose up without an explicit networks: block. Docker-Compose creates a default network (project_default) for all services. But if one service has network_mode: host, it gets kicked out of that default network. The result? The host-mode service can’t resolve other containers by name, but those containers can reach the host-mode service via localhost.

This asymmetric access pattern is the most common thing I see people miss during debugging.


4. The Fix: Step-by-Step Resolution

# Step 1: Create a user-defined bridge network
docker network create --driver bridge --subnet 172.22.0.0/16 my_app_net

# Step 2: Start containers on that network
docker run -d --name adguardhome --network my_app_net \
  -p 53:53/tcp -p 53:53/udp -p 784:784/udp -p 853:853/tcp \
  -p 3000:3000/tcp \
  adguard/adguardhome

docker run -d --name portainer --network my_app_net \
  -p 8000:8000 -p 9443:9443 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  portainer/portainer-ce

# Step 3: Verify DNS resolution
docker exec adguardhome nslookup portainer 127.0.0.11
# Output: Name: portainer, Address: 172.22.0.3

4.2 Solution 2: Docker Compose (Cleaner)

version: '3.8'

services:
  adguardhome:
    image: adguard/adguardhome
    container_name: adguardhome
    networks:
      - app_network
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "784:784/udp"
      - "853:853/tcp"
      - "3000:3000/tcp"

  portainer:
    image: portainer/portainer-ce
    container_name: portainer
    networks:
      - app_network
    ports:
      - "8000:8000"
      - "9443:9443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

networks:
  app_network:
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/16

Run it: docker-compose up -d

4.3 Solution 3: Fixing Host Network Mode Containers

If your use case demands host networking (e.g., capturing all network interface traffic), you need to manually add the container back to a custom network:

# Start with host mode, then attach to custom network
docker run -d --name adguardhome --network host adguard/adguardhome
docker network connect my_app_net adguardhome

# Now adguardhome has both host AND custom network access
# Other containers on my_app_net can reach it by name
# But adguardhome needs to use the custom network IP to reach others

Caveat: /etc/resolv.conf still shows external DNS. You’ll need to either manually set DNS or use --dns on the custom network.


5. Performance and Security Trade-offs

5.1 Embedded DNS vs. External DNS

MetricDocker Embedded DNS (127.0.0.11)External DNS (e.g., 8.8.8.8)
Container name resolution✅ Supported❌ Not supported
Resolution latency~0.5ms (loopback)~10-30ms (network round-trip)
ScalabilitySingle-node onlyCross-node capable
Cache policyTTL-based, default 600sDepends on upstream
Failure modeAll container DNS breaksExternal access breaks, internal still works

There’s a nasty edge case here. If your host’s /etc/resolv.conf has a search domain (like search example.com), containers inherit it. When you query a container name, Docker’s embedded DNS tries containername, then containername.example.com. If your external DNS can resolve containername.example.com to some random IP, you get a false positive — everything looks like it’s working, but you’re talking to the wrong host.

5.2 The “Gateway Hijack” Problem

Someone in the Reddit thread suggested “attaching other containers to the DNS stack’s bridged network and explicitly setting its gateway.” This points to a more insidious bug: if the custom network’s gateway is set to another container’s IP (like Pi-hole’s IP), DNS requests get routed to that container, which then forwards them back in a loop.

Fix: always set an explicit gateway:

docker network create \
  --driver bridge \
  --subnet 172.22.0.0/16 \
  --gateway 172.22.0.1 \
  my_app_net

6. Alternatives and Trade-offs

links has been deprecated since Docker Compose V3, but people still use it. It writes static entries to /etc/hosts:

services:
  app:
    image: myapp
    links:
      - db:database

Problem: container rebuilds change IPs, but /etc/hosts doesn’t update automatically. If service startup order shifts, DNS breaks silently.

6.2 Nginx Reverse Proxy with Unix Sockets

Some people use Nginx as a reverse proxy over Unix sockets instead of network ports. Better performance, more secure — but both containers must be on the same host, and the config is gnarly:

upstream backend {
    server unix:/var/run/docker.sock:/var/run/portainer.sock;
}

6.3 Just Use Kubernetes Already?

For two containers? That’s like using a flamethrower to light a candle. But if you’ve got 10+ services that need service discovery, K8s’s CoreDNS is rock-solid compared to Docker’s built-in DNS. The cost: a massive control plane overhead, a brutal learning curve, and at least 2GB RAM reserved for K8s components.


7. Summary

Docker container DNS failures boil down to two causes:

  1. Containers aren’t on the same user-defined network.
  2. host network mode prevents DNS injection.

The fix is trivially simple:

  • Create a custom network → assign containers at startup → done.
  • Don’t use --link.
  • Don’t expect container name resolution to work automatically in host mode.

If you’re still hitting this, run docker inspect to check the network config, then cat /etc/resolv.conf to confirm the DNS target. Ten minutes of methodical checking and you’ll have it nailed.


References & Community Insights


FAQ

Q: Two containers on the same host can’t communicate by container name. Why? A: Because they aren’t connected to the same user-defined bridge network. The default docker0 bridge doesn’t support automatic DNS resolution by container name. Create a custom network (docker network create) and specify it with --network at container startup.

Q: Does the --link flag fix DNS resolution? A: Temporarily, but --link has been deprecated since Docker 1.9. It writes static entries to /etc/hosts that don’t update when container IPs change on restart. The recommended solution is user-defined networks.

Q: Why does Docker’s embedded DNS use 127.0.0.11? A: Docker’s embedded DNS server listens on 127.0.0.11:53. The 127.0.0.11 address is a reserved loopback IP in Docker’s network namespace, allowing containers to reach the DNS service via the loopback interface without external network dependencies.

Q: Why does DNS break when a container uses --network host? A: In host network mode, the container shares the host’s network stack. Docker doesn’t modify /etc/resolv.conf and doesn’t inject 127.0.0.11 as the nameserver. Fix it by running docker network connect to attach the container to a custom network after startup.

Q: How do I verify Docker container DNS is working? A: Run docker exec <container> nslookup <target-container> 127.0.0.11 or docker exec <container> getent hosts <target-container>. If it returns an IP address, DNS is working. If it returns “not found” or a timeout, your network config is wrong.

Elvin Hui

About the Author: Elvin Hui

Elvin is a Senior Infrastructure Engineer with 10+ years of experience spanning data centers, cloud-native architecture, and network security. Certified in CCNA and AWS Solutions Architecture, I focus on turning real-world production "war stories" into actionable, hardcore technical guides.