Skip to content

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-net

Docker 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 ls

You’ll see something similar to:

NETWORK ID     NAME      DRIVER    SCOPE
...            bridge    bridge    local
...            host      host      local
...            none      null      local
...            app-net   bridge    local

Notice something important:

NAME       DRIVER
app-net    bridge

app-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
  Database

You 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 300

Now both containers belong to the same user-defined bridge:

                    app-net
              ┌─────────────────┐
              │                 │
        ┌─────▼─────┐     ┌─────▼─────┐
        │  server   │     │  client   │
        │ nginx     │     │ alpine    │
        └───────────┘     └───────────┘
              │                 │
              └───────┬─────────┘
                      │
                 bridge network

You can inspect the network:

docker network inspect app-net

The 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://server

This 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://server

Docker 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 / Internet

The 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.2

It works today.

Then the database container is recreated.

Docker may give the replacement container a different IP:

172.18.0.4

Now the application has stale configuration.

Compare that with:

DATABASE_HOST=database

The 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:alpine

Other containers on app-net can resolve:

database

Docker 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:alpine

Now the container can be reached using the alias:

database

or:

db

This 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:80

for client to reach it.

Because both containers are already on the same Docker network, client can connect directly to:

http://server:80

The path is roughly:

client
  |
  |  http://server:80
  v
Docker DNS
  |
  |  server → container IP
  v
app-net bridge
  |
  v
server:80

Port publishing solves a different problem:

Host / external machine → container

A Docker network solves:

Container → container

So 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      → cache

Instead 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-net

Then connect an existing container to it:

docker network connect monitoring-net api

Now 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 network

The 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:alpine

The container is created and connected to app-net immediately.

Connect an Existing Container

If the container already exists:

docker network connect app-net server

Now 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 network

We’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 → database

and app resolves:

database → 172.18.0.2

Now you remove and recreate database.

The new container might receive:

database → 172.18.0.5

The application still asks for:

database

It 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 ports

These solve different problems.

Default Bridge vs User-Defined Bridge

Now the difference should be much clearer:

ScopeDefault bridgeUser-defined bridge
Created byDockerYou
Driverbridgebridge
Containers automatically joinYes, when no network is specifiedNo
Container-to-container connectivityYesYes
Automatic container-name DNSNoYes
Separate application networksNot naturallyYes
Can attach existing containersYesYes
Recommended for multi-container applicationsNoYes

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 bridge

If 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 server

Then remove the network:

docker network rm app-net

If you also created monitoring-net during experimentation, remove it too:

docker network rm monitoring-net

The 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.

Last updated on