Dash0 Raises $110M Series B at $1B Valuation

  • 11 min read

How to Ping Between Docker Containers

You ran two containers, both are up, and one can't ping the other by name. Nine times out of ten, both containers are sitting on Docker's default bridge network, which deliberately does not resolve container names through DNS. The fix is to put them on a user-defined network, which gives you automatic name-based DNS for free.

The fix: a user-defined bridge network

Create a network, run both containers on it, and use container names as hostnames:

bash
123456789
# Create the network
docker network create my-net
# Run two containers attached to it
docker run -d --name web --network my-net nginx:latest
docker run -d --name client --network my-net alpine sleep infinity
# Ping by name
docker exec client ping -c 3 web

You should see something like this:

1234
PING web (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.067 ms
64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.090 ms
64 bytes from 172.18.0.2: seq=2 ttl=64 time=0.075 ms

That's it. Both containers can now resolve each other by container name (web, client) and by any aliases you assign with --network-alias. The names are stable across container restarts, which IP addresses are not.

If the containers are already running and you don't want to recreate them, attach them to the network instead:

bash
12
docker network connect my-net existing-container-1
docker network connect my-net existing-container-2

Both containers keep their original network attachments. They just gain an additional interface on my-net and can reach each other through it.

Why the default bridge network doesn't work by name

When you run a container without --network, it attaches to a built-in network simply called bridge. On that network, containers can reach each other by IP address, but Docker does not run its embedded DNS server there. To see this for yourself, run a fresh container on the default bridge (under a different name so it doesn't conflict with the web from above) and try to ping it:

bash
12
docker run -d --name web-default nginx:latest
docker run --rm alpine ping -c 1 web-default
1
ping: bad address 'web-default'

That's intentional. The default bridge exists for backward compatibility with the original Docker networking model from before user-defined networks were a thing. Back then, the way to link containers was the --link flag, which is now legacy and not recommended. If you need name-based DNS, Docker expects you to opt into it by creating a user-defined network.

User-defined networks work because Docker runs an embedded DNS server at 127.0.0.11 inside every container attached to one. You can confirm it's there:

bash
1
docker exec client cat /etc/resolv.conf
12
nameserver 127.0.0.11
options ndots:0

That nameserver knows about every other container on the same network, resolves their names to current IPs in real time, and forwards anything else to the host's upstream DNS. None of this exists on the default bridge.

When ping fails on a user-defined network

If both containers are on the same user-defined network and ping still doesn't work, walk through these checks in order.

  1. Verify they're actually on the same network. A container can be attached to multiple networks at once, and it's easy to assume two containers share one when they don't:
bash
12
docker network inspect my-net \
--format '{{range .Containers}}{{.Name}} {{.IPv4Address}}{{"\n"}}{{end}}'
12
client 172.18.0.3/16
web 172.18.0.2/16
1
If a container you expected isn't listed, attach it with `docker network connect my-net <container>`.

2. Check whether the image has ping at all. Many minimal images (distroless, scratch-based, or vendor *-slim variants) ship without it. If your container is one of these (call it api here), you'll see this:

bash
1
docker exec api ping web

For Alpine derivatives without it, install with apk add iputils-ping. For distroless images you can't install anything, so exec in a debugging sidecar instead:

bash
1
docker run --rm --network my-net nicolaka/netshoot ping -c 3 web

The netshoot image bundles ping, nslookup, dig, curl, tcpdump, and most of the tools you'd want for poking at container networking, without polluting your real container.

  1. Test DNS resolution directly. If ping can't resolve the name but the network membership looks right, check whether the embedded DNS server is responding:
bash
1
docker exec client nslookup web
123456
Server: 127.0.0.11
Address: 127.0.0.11:53
Non-authoritative answer:
Name: web
Address: 172.18.0.2

If nslookup resolves the name but ping doesn't, the issue is ICMP, not DNS. Some hardened hosts drop ICMP between container network namespaces, and some container images strip the CAP_NET_RAW capability that ping needs. Test a real port instead. Alpine's BusyBox ships with nc, so that works directly from client; curl is not in stock Alpine, so either install it with apk add curl or run it from the netshoot sidecar:

bash
12
docker exec client nc -zv web 80
docker run --rm --network my-net nicolaka/netshoot curl -sI http://web
123
HTTP/1.1 200 OK
Server: nginx/1.31.1
...

For most real debugging, "can the app reach the other service on its port" is the question you actually care about, and ICMP just happened to be the convenient first probe. If the port responds but the application still doesn't behave, the next stop is the container's own logs.

A note on Docker Compose

If you're using Docker Compose, none of this network setup is necessary. Compose creates a user-defined bridge network per project automatically and attaches every service to it. Service names become hostnames inside that network:

yaml
12345
services:
api:
image: myapi:latest
database:
image: postgres:16

From inside the api container, ping database and curl http://database:5432 both resolve correctly. This is the most common reason developers don't hit the default-bridge gotcha until they go back to plain docker run for one-off debugging.

You can inspect the auto-created network the same way as any other:

bash
12
docker network ls
docker network inspect <project-name>_default

Compose also aggregates logs from every service into a single stream; see our Docker Compose logs guide for the patterns that work in production.

Common pitfalls

  • Container IPs are not stable. When a container stops, its IP is released back to the network's pool, and the next container to start can claim it. The same applies when you restart a container: the new IP usually won't match the old one. Always reference containers by name, never by an IP you looked up five minutes ago.

  • Different networks are isolated. Containers on different networks can't reach each other, even on the same Docker host. If you ran web with --network frontend and db with --network backend, they're isolated by design. To bridge them, either attach one container to both networks with docker network connect, or put them on a shared network from the start.

  • Don't use the --link flag. It still works on the default bridge, but don't use it. It's been deprecated for years, it only functions on the default bridge, and it doesn't survive container restarts cleanly. Any guide recommending it is out of date.

  • Use --network-alias for stable hostnames. If you can't change a container's name (maybe your orchestrator generates them), assign a stable alias with --network-alias api at run time. Other containers can then reach it as api regardless of the actual container name. This is also useful when you want multiple containers to respond to the same hostname, since Docker will round-robin across them.

Final thoughts

Container-to-container networking has one major quirk (the default bridge behaving differently from user-defined networks) that sends people down hours of debugging when the fix is a one-line docker network create. Once you know to look for it, it becomes a 30-second check.

In production, the harder questions come after the network works: which service called which, how long did it take, where did the request fail. Once you scale past a few containers, docker logs becomes too ephemeral to help and docker stats only gives you a single point in time. That's when you need real observability.

Dash0's infrastructure monitoring ties container metrics together with real-time logs and distributed traces, so you can see exactly which container talked to which, on which port, and where the latency came from. Start a free trial to see your container traffic, logs, and traces in one view. No credit card required.