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