User Defined Bridge Network
You’ve just seen the awkward part of Docker’s default bridge: containers can communicate, but if one container needs to find another, you’re pushed toward using its IP address. That is exactly the kind of detail an application shouldn’t have to care about.
A user-defined bridge network fixes that. You still get the same basic bridge idea — containers connected through a software switch — but now you control which containers belong to the network, and Docker provides automatic DNS-based name resolution between them.
Create Your Own Bridge
The command is straightforward:
docker network create app-netDocker creates a new network named app-net.
Because we didn’t specify a driver, Docker uses the bridge driver by default.
You can verify it with:
docker network lsYou’ll see something similar to:
NETWORK ID NAME DRIVER SCOPE
... bridge bridge local
... host host local
... none null local
... app-net bridge localNotice something important:
NAME DRIVER
app-net bridgeapp-net is the network we created.
bridge is the driver implementing it.
So a user-defined bridge network is not a completely different networking mechanism. It is your own bridge network, created and managed separately from Docker’s built-in default bridge.
Why Create Another Bridge?
The default bridge is shared automatically by containers that don’t specify another network.
That is convenient for quick experiments, but it isn’t a great way to describe an application’s architecture.
Suppose you have:
Web application
|
v
DatabaseYou want those two containers to belong to the same application network.
With a user-defined network, you can express that directly:
app-net
┌──────────────────────────────────┐
│ │
│ Web container │
│ │ │
│ │ │
│ Database container │
│ │
└──────────────────────────────────┘And if you have another unrelated application:
app-net other-net
┌───────────────┐ ┌───────────────┐
│ web │ │ frontend │
│ database │ │ database │
└───────────────┘ └───────────────┘The network itself becomes a way to group containers that need to communicate.
This is one of the biggest practical advantages of user-defined networks:
You decide which containers share a network instead of putting every unspecified container onto one common default bridge.
Connect Containers to the Network
Let’s create two containers and explicitly attach them to app-net:
docker container run -d \
--name server \
--network app-net \
nginx:alpine
docker container run -d \
--name client \
--network app-net \
alpine:latest sleep 300Now both containers belong to the same user-defined bridge:
app-net
┌─────────────────┐
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ server │ │ client │
│ nginx │ │ alpine │
└───────────┘ └───────────┘
│ │
└───────┬─────────┘
│
bridge networkYou can inspect the network:
docker network inspect app-netThe output includes the containers attached to it.
So far, this may look almost identical to the default bridge.
The important difference is what happens when client tries to find server.
The Important Difference: Docker DNS
On a user-defined bridge network, Docker provides automatic DNS resolution between containers.
That means the container name can act as a hostname.
We can test it.
From client, request server by name:
docker container exec client wget -qO- http://serverThis time, it works.
There is no IP address in the command.
The flow is roughly:
sequenceDiagram
participant C as client
participant D as Docker DNS
participant S as server
C->>D: Who is "server"?
D-->>C: server = container IP
C->>S: HTTP request
S-->>C: nginx response
The application doesn’t need to know the IP address.
It only needs to know the service’s name:
http://serverDocker handles the name-to-IP lookup.
That is the feature the default bridge was missing.
The Mental Model to Remember
The most useful diagram from the previous lesson still applies.
A bridge is still a software switch:
flowchart LR
C1["Container A<br/>server"] --- V1["virtual interface"]
C2["Container B<br/>client"] --- V2["virtual interface"]
V1 --- B["app-net<br/>user-defined bridge"]
V2 --- B
B --- D["Docker DNS"]
B --- H["Host network stack"]
H --- N["NAT / routing"]
N --- I((Internet))
There is now one important extra piece to remember:
user-defined bridge → Docker DNS → container name
So the mental model becomes:
Container
|
v
User-defined bridge
|
+── other containers
|
+── Docker DNS
|
+── host / InternetThe bridge still provides connectivity.
Docker DNS gives that connectivity something much nicer to use: names instead of changing IP addresses.
Names Are Better Than IP Addresses
Imagine your application is configured like this:
DATABASE_HOST=172.18.0.2It works today.
Then the database container is recreated.
Docker may give the replacement container a different IP:
172.18.0.4Now the application has stale configuration.
Compare that with:
DATABASE_HOST=databaseThe application doesn’t care which IP Docker currently assigns to database.
Docker resolves the name to the current container address.
That’s the real value of Docker’s built-in DNS here.
Tip
Think of a container name on a user-defined network as a stable network identity, while the container IP is an implementation detail that can change when the container is recreated.
Container Name vs Network Alias
The simplest case is the container name.
If you create:
docker container run -d \
--name database \
--network app-net \
nginx:alpineOther containers on app-net can resolve:
databaseDocker also supports network aliases, which let a container be reachable through an additional name.
For example:
docker container run -d \
--name database \
--network app-net \
--network-alias db \
nginx:alpineNow the container can be reached using the alias:
databaseor:
dbThis is useful when you want the network identity to describe a service rather than the specific container name.
For now, though, container names are enough to understand the main idea.
Containers Don’t Need Published Ports to Talk
There is another important distinction worth making.
Suppose server is running nginx on port 80.
This does not require:
--publish 8080:80for client to reach it.
Because both containers are already on the same Docker network, client can connect directly to:
http://server:80The path is roughly:
client
|
| http://server:80
v
Docker DNS
|
| server → container IP
v
app-net bridge
|
v
server:80Port publishing solves a different problem:
Host / external machine → containerA Docker network solves:
Container → containerSo don’t add --publish just because one container needs to talk to another.
One Network Can Contain Many Containers
A user-defined bridge isn’t limited to two containers.
A typical small application might look like:
flowchart LR
Web["web"] --- N["app-net<br/>bridge"]
API["api"] --- N
DB["database"] --- N
Cache["cache"] --- N
Now the containers can use names:
web → api
api → database
api → cacheInstead of maintaining a list of container IP addresses.
This starts to look much more like an actual application architecture.
And because the network is user-defined, you can create another network for another application:
flowchart TB
subgraph AppA["app-net"]
A1["web"]
A2["api"]
A3["database"]
end
subgraph AppB["other-net"]
B1["web"]
B2["database"]
end
The networks provide a natural boundary between the two groups.
Containers Can Belong to More Than One Network
A container isn’t necessarily limited to one network.
You can create another network:
docker network create monitoring-netThen connect an existing container to it:
docker network connect monitoring-net apiNow api belongs to both networks:
app-net
┌─────────────┐
│ │
web ─┤ ├─ database
│ api │
└──────┬──────┘
│
│
monitoring-net
┌──────▼──────┐
│ │
│ monitor │
└─────────────┘This becomes useful when a container needs to communicate with two separate groups.
For example:
application network
+
monitoring networkThe container has a network interface for each attached network.
You don’t need to use multiple networks for simple examples, but knowing that this is possible helps explain why user-defined networks are more flexible than the default bridge.
Create First or Attach During Run?
There are two common ways to put a container on a user-defined network.
Specify the Network When Creating the Container
This is usually the simplest:
docker container run -d \
--name server \
--network app-net \
nginx:alpineThe container is created and connected to app-net immediately.
Connect an Existing Container
If the container already exists:
docker network connect app-net serverNow server is attached to the network.
This is useful when you need to change the network arrangement of an existing container.
The important commands are therefore:
docker network create
↓
create a network
docker container run --network
↓
create a container on that network
docker network connect
↓
attach an existing container
docker network disconnect
↓
remove a container from a networkWe’ll cover the network-management commands more systematically later.
What Happens to DNS When a Container Is Recreated?
This is where the name-based model becomes particularly useful.
Suppose:
app → databaseand app resolves:
database → 172.18.0.2Now you remove and recreate database.
The new container might receive:
database → 172.18.0.5The application still asks for:
databaseIt doesn’t need to know that the IP changed.
Docker’s DNS resolves the current address for the container on the network.
That is why the name is a better thing for your application to depend on.
What User-Defined Bridge Does Not Mean
It’s easy to accidentally build the wrong mental model.
A user-defined bridge does not mean:
- containers are magically exposed to the Internet
- every container on your host can automatically reach them by name
- the container gets a permanent IP address
- port publishing is no longer useful
- the bridge itself is doing DNS
The useful mental separation is:
Bridge
↓
Provides network connectivity
Docker DNS
↓
Provides name resolution
Port publishing
↓
Provides host/external access to container portsThese solve different problems.
Default Bridge vs User-Defined Bridge
Now the difference should be much clearer:
| Scope | Default bridge | User-defined bridge |
|---|---|---|
| Created by | Docker | You |
| Driver | bridge | bridge |
| Containers automatically join | Yes, when no network is specified | No |
| Container-to-container connectivity | Yes | Yes |
| Automatic container-name DNS | No | Yes |
| Separate application networks | Not naturally | Yes |
| Can attach existing containers | Yes | Yes |
| Recommended for multi-container applications | No | Yes |
The key difference isn’t that one is a bridge and the other isn’t.
Both are bridges.
The difference is how Docker manages them and what features you get around them.
A Simple Rule
For everyday Docker work, a useful rule is:
Quick container experiment
↓
Default bridge is fine
Multiple containers forming an application
↓
Create a user-defined bridgeIf you find yourself saying:
“Container A needs to connect to Container B, and I don’t want to hard-code B’s IP.”
that’s a strong signal that you want a user-defined bridge network.
Clean Up
Remove the containers from the experiment:
docker container rm -f client serverThen remove the network:
docker network rm app-netIf you also created monitoring-net during experimentation, remove it too:
docker network rm monitoring-netThe Bridge Mental Model
At this point, keep one picture in your head:
Docker Host
┌─────────────────────────────────────────────────────┐
│ │
│ Container A Container B │
│ ┌──────────┐ ┌──────────┐ │
│ │ eth0 │ │ eth0 │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ └────────────┬───────────────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ app-net │ │
│ │ bridge │ │
│ └──────┬──────┘ │
│ │ │
│ ┌──────────┴──────────┐ │
│ │ │ │
│ Docker DNS Host / NAT │
│ │ │ │
│ "server" Internet │
│ │ │
│ ▼ │
│ Container IP │
│ │
└─────────────────────────────────────────────────────┘Remember it as:
Bridge gives containers a path to each other. Docker DNS lets them use names.
That one sentence captures why user-defined bridge networks are so useful.
What’s Next
We’ve now created a bridge network and seen why it is better than the default bridge for multi-container applications.
But we’ve only tested individual pieces.
Next, we’ll put those pieces together in a small real-world multi-container application and use a user-defined bridge for the problem it actually solves: letting application components discover and communicate with each other without caring about container IP addresses.