How does the default bridge network provide container-to-container communication, and why is it usually not the best choice?
Quick Answer
The default bridge network (docker0) lets containers reach each other by IP address, and gives outbound internet access via NAT. But it does not support DNS-based service discovery by container name, unlike a user-defined bridge network. So containers on the default bridge must be hardcoded to reach each other by IP, which can change on restart. This is why Docker recommends always creating a custom, user-defined bridge network instead.
Detailed Answer
What happens without specifying a network
docker run -d --name web nginx
docker run -d --name api myapi:1.0
Without an explicit --network flag, both containers attach to Docker's default bridge network — visible as the docker0 interface on the host. Each gets its own private IP address on this virtual network, and can reach the internet through NAT via the host.
The critical limitation: no built-in DNS resolution by name
docker exec web ping api
# ping: api: Name or service not known
On the default bridge network, containers cannot resolve each other by container name. You'd have to look up api's current IP manually with docker inspect api, then hardcode that IP into web's configuration. This is fragile — a container's IP on the default bridge can change if it's stopped and restarted.
The fix: a user-defined bridge network
docker network create my-app-network
docker run -d --network my-app-network --name web nginx
docker run -d --network my-app-network --name api myapi:1.0
docker exec web ping api
# PING api (172.20.0.3): 56 data bytes <- resolves correctly by name!
A user-defined bridge network, created with docker network create, includes Docker's embedded DNS server automatically. Containers on that network can resolve each other by container name, or by any --network-alias assigned. This is the single biggest reason the default bridge is discouraged for real multi-container apps.
Additional benefits of user-defined networks over the default
- Better isolation. You can create multiple separate user-defined networks. Containers only see or reach others explicitly attached to the same network. For example, you could set up a "frontend" network and a "backend" network, with only specific containers bridging both. The shared default bridge can't do this.
- Dynamic reconnection. A running container can be connected to or disconnected from a user-defined network on the fly, with
docker network connect/disconnect, without a restart. The default bridge is more rigid about this. - This is exactly what Docker Compose does automatically. Every
docker-compose.ymlproject gets its own user-defined bridge network by default. That's why Compose services can reference each other by service name out of the box, with no manual network setup.
The default bridge is really only useful for the simplest case: a single container with no inter-container communication, or a legacy setup. Anything with more than one container talking to each other should use a user-defined network instead.