---
title: "Docker Networking Explained: Bridge, Host and Overlay"
description: "Understand Docker network drivers on Ubuntu 24.04: create bridge networks with DNS, publish ports safely, use host and none modes, and set up overlay networks."
category: "Docker Kubernetes"
language: en
source: https://cubepath.com/docs/docker-kubernetes/docker-networking-bridge-host-overlay
updated: 2026-09-25
---
# Docker Networking Explained: Bridge, Host and Overlay
Every Docker container gets its networking from a **network driver**. The driver decides whether a container has its own isolated network stack, shares the host's, or joins a network that spans several servers. In this tutorial you will inspect the default networks on Ubuntu 24.04, create a user-defined bridge network where containers find each other by name, publish ports without exposing them by accident, and try the host, none and overlay drivers so you know when each one fits.
## Prerequisites
To follow this tutorial you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
- A non-root user with `sudo` privileges, added to the `docker` group.
- Docker Engine installed from Docker's official repository.
- For the overlay section only: a second server running Docker that can reach the first one over the network.
## Docker network drivers at a glance
| Driver | Scope | Isolation | Typical use |
|---|---|---|---|
| `bridge` | Single host | Own network namespace, NAT to the outside | Default for most containers and Compose projects |
| `host` | Single host | None: shares the host network stack | Maximum network performance, apps that need many ports |
| `none` | Single host | Loopback only | Batch jobs that must not touch the network |
| `overlay` | Multi-host | VXLAN tunnel between Docker hosts | Docker Swarm services across several servers |
| `macvlan` / `ipvlan` | Single host | Container gets an address on the physical LAN | Legacy apps that must appear as a physical device |
## Step 1 - Inspecting the default networks
Docker creates three networks when it is installed:
```bash
docker network ls
```
```output
NETWORK ID NAME DRIVER SCOPE
3c1f2a9b8d7e bridge bridge local
9a8b7c6d5e4f host host local
1a2b3c4d5e6f none null local
```
The `bridge` network is backed by the `docker0` interface on the host. Check its subnet:
```bash
ip -brief address show docker0
```
```output
docker0 DOWN 172.17.0.1/16
```
The interface shows `DOWN` until a container is attached to it. Every container started without `--network` joins this default bridge and receives an address from `172.17.0.0/16`.
The default bridge has one important limitation: containers on it **cannot resolve each other by name**. Try it:
```bash
docker run -d --name web1 nginx:alpine
docker run --rm alpine ping -c 1 web1
```
```output
ping: bad address 'web1'
```
That is why you should create your own networks for anything beyond a single throwaway container. Remove the test container:
```bash
docker rm -f web1
```
## Step 2 - Creating a user-defined bridge network
A user-defined bridge network provides an embedded DNS server, so containers reach each other by container name. It also isolates the containers on it from those on other networks.
Create a network with an explicit subnet. Specifying one is optional, but it avoids surprises when Docker's automatic choice overlaps with a private range you already use:
```bash
docker network create --driver bridge --subnet 172.20.0.0/24 app-net
```
Start an Nginx container on it, then test name resolution from a second container on the same network:
```bash
docker run -d --name web --network app-net nginx:alpine
docker run --rm --network app-net alpine ping -c 2 web
```
```output
PING web (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.101 ms
64 bytes from 172.20.0.2: seq=1 ttl=64 time=0.087 ms
```
The name resolves. Containers use Docker's embedded DNS server at `127.0.0.11`, which you can see in any container on the network:
```bash
docker run --rm --network app-net alpine cat /etc/resolv.conf
```
```output
nameserver 127.0.0.11
options ndots:0
```
Fetch the Nginx page through the network to confirm connectivity at the application level:
```bash
docker run --rm --network app-net alpine wget -qO- http://web | head -4
```
```output
Welcome to nginx!
```
### Adding network aliases
An alias gives a container an additional DNS name on a network. Several containers can share one alias, and Docker's DNS returns all of their addresses, which gives you simple round-robin between replicas:
```bash
docker run -d --name api1 --network app-net --network-alias api nginx:alpine
docker run -d --name api2 --network app-net --network-alias api nginx:alpine
docker run --rm --network app-net alpine nslookup api
```
The answer lists two addresses, one per container.
### Connecting a container to several networks
A container can be attached to more than one network, which is how you let a reverse proxy talk to a backend without putting every container on the same network:
```bash
docker network create frontend-net
docker network connect frontend-net web
docker inspect --format '{{range $name, $_ := .NetworkSettings.Networks}}{{$name}} {{end}}' web
```
```output
app-net frontend-net
```
Detach it again with `docker network disconnect frontend-net web`.
### Creating an internal network
A network created with `--internal` has no route to the outside world. It is useful for databases that should only be reachable by application containers:
```bash
docker network create --internal db-net
docker run --rm --network db-net alpine ping -c 1 -W 2 1.1.1.1
```
```output
PING 1.1.1.1 (1.1.1.1): 56 data bytes
ping: sendto: Network unreachable
```
## Step 3 - Publishing ports safely
Containers on a bridge network are not reachable from outside the host until you publish a port with `-p host_port:container_port`. The form of the flag decides who can connect:
```bash
docker run -d --name public-web -p 8080:80 nginx:alpine
docker run -d --name local-web -p 127.0.0.1:8081:80 nginx:alpine
```
List the mappings:
```bash
docker port public-web
docker port local-web
```
```output
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080
80/tcp -> 127.0.0.1:8081
```
`public-web` listens on every interface, including your public IP. `local-web` is only reachable from the server itself, which is the right choice for services behind a reverse proxy.
> Warning: Docker programs its own iptables rules for published ports, and those rules are evaluated before UFW's. A port published as `-p 8080:80` is reachable from the internet even if `sudo ufw status` shows it as blocked. Bind to `127.0.0.1` (or a private IP) for anything that should not be public.
Test both from the server:
```bash
curl -sI http://127.0.0.1:8080 | head -1
curl -sI http://127.0.0.1:8081 | head -1
```
```output
HTTP/1.1 200 OK
HTTP/1.1 200 OK
```
From another machine, only `http://your_server_ip:8080` answers. Clean up:
```bash
docker rm -f public-web local-web api1 api2
```
## Step 4 - Using the host network
With `--network host` the container shares the host's network stack: no separate IP, no NAT, and no port mapping. A process listening on port 80 inside the container listens on port 80 of the server:
```bash
docker run -d --name host-web --network host nginx:alpine
curl -sI http://127.0.0.1 | head -1
```
```output
HTTP/1.1 200 OK
```
Any `-p` flags are ignored in this mode, and the container's ports collide with services on the host. Use host networking for software that needs many dynamic ports or the lowest possible network overhead (for example some monitoring agents), not as a shortcut to avoid port mapping. It removes a layer of isolation. Remove the container:
```bash
docker rm -f host-web
```
## Step 5 - Using the none network
The `none` network gives the container only a loopback interface:
```bash
docker run --rm --network none alpine ip -brief address
```
```output
lo UNKNOWN 127.0.0.1/8 ::1/128
```
Use it for jobs that process local files and must not send or receive traffic.
## Step 6 - Creating an overlay network across two hosts
Overlay networks connect containers running on different Docker hosts through a VXLAN tunnel. They require Docker Swarm mode, even if you only use Swarm for networking.
Between the two servers, allow these ports (on the private network if you have one):
- `2377/tcp` for cluster management
- `7946/tcp` and `7946/udp` for node communication
- `4789/udp` for overlay (VXLAN) traffic
On Ubuntu with UFW, run this on both servers, replacing `other_server_ip` with the address of the other node:
```bash
sudo ufw allow from other_server_ip to any port 2377 proto tcp
sudo ufw allow from other_server_ip to any port 7946
sudo ufw allow from other_server_ip to any port 4789 proto udp
```
On the first server, initialize Swarm and advertise the address the other node will use to reach it:
```bash
docker swarm init --advertise-addr your_server_ip
```
The output includes a `docker swarm join --token ...` command. Run that exact command on the second server. Then, back on the first server (the manager), confirm both nodes are ready:
```bash
docker node ls
```
```output
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION
p1x7k2... * node1 Ready Active Leader 28.3.3
m9d3f5... node2 Ready Active 28.3.3
```
Create an attachable overlay network. `--attachable` lets standalone containers (not only Swarm services) join it, and `--opt encrypted` encrypts VXLAN traffic between nodes with IPsec:
```bash
docker network create --driver overlay --attachable --opt encrypted multi-net
```
Start a container on the first server:
```bash
docker run -d --name remote-web --network multi-net nginx:alpine
```
On the second server, reach it by name through the overlay:
```bash
docker run --rm --network multi-net alpine wget -qO- http://remote-web | grep title
```
```output
Welcome to nginx!
```
The request crossed hosts through the overlay tunnel. Clean up on the first server with `docker rm -f remote-web` and `docker network rm multi-net`. If you do not need Swarm afterwards, run `docker swarm leave` on the second server and `docker swarm leave --force` on the first.
## A note on macvlan
The `macvlan` driver gives each container its own MAC address and an IP on the physical network, so it looks like a separate machine on the LAN. It is useful on bare-metal or on-premises networks you control. On most cloud VPS platforms the upstream network only accepts traffic from the MAC address assigned to the virtual machine, so macvlan containers get no connectivity. Prefer a bridge network with published ports there.
## Troubleshooting
**`Pool overlaps with other one on this address space`.** The subnet you passed to `docker network create` is already used by another Docker network. List subnets with `docker network inspect $(docker network ls -q) --format '{{.Name}} {{range .IPAM.Config}}{{.Subnet}}{{end}}'` and pick a free range.
**`error while removing network: network app-net has active endpoints`.** Containers are still attached. Find them with `docker network inspect app-net --format '{{range .Containers}}{{.Name}} {{end}}'`, remove or disconnect them, then retry.
**Containers cannot resolve each other by name.** Confirm both are on the same user-defined network (not the default `bridge`) with `docker inspect --format '{{json .NetworkSettings.Networks}}' container_name`.
**Containers have no internet access.** Check that IP forwarding is enabled (`sysctl net.ipv4.ip_forward` must return `1`) and that the network was not created with `--internal`. Restarting Docker with `sudo systemctl restart docker` recreates its iptables rules if something flushed them.
To remove every network not used by at least one container, run `docker network prune`.
## Conclusion
You now know how Docker's network drivers behave: user-defined bridge networks for name-based communication on one host, careful port publishing to avoid bypassing UFW, host and none modes for special cases, and overlay networks for containers spread across several servers. As next steps, describe your networks in a `compose.yaml` file so they are created with your stack, put a reverse proxy on a shared network in front of your applications, and read our guide on Docker logs and troubleshooting to debug connectivity problems.