Networking

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production; see the section bibliography for the reference material consulted while preparing these pages.

Every container gets its own network namespace — its own interfaces, routing table and ports — and Docker connects those namespaces to each other and to the outside world through networks. Learning Docker (chapter 6) explains this through the docker0 bridge and NAT, and chapter 8 through --link. Both are still true at the lowest level, but the model you use today is different: user-defined networks with built-in DNS. This page describes networking in Docker Engine 29.8.x.

Network drivers

Driver What it does Use it for

bridge (default bridge)

The pre-created network named bridge, backed by the docker0 Linux bridge. Containers get private IPs and reach the outside world through NAT. No DNS between containers.

Nothing new — it exists for backward compatibility

bridge (user-defined)

A bridge you create with docker network create. Adds embedded DNS (containers resolve each other by name and alias), better isolation and hot attach/detach.

Every multi-container application on one host (Compose creates one per project)

host

No network isolation: the container shares the host’s network namespace. -p is ignored.

Maximum network performance, tools that must see host interfaces

none

Only a loopback interface.

Batch jobs that must not have network access

overlay

A virtual network spanning several Docker hosts (VXLAN), used by Swarm services.

Multi-host clusters (see Orchestration)

macvlan / ipvlan

Give containers addresses directly on the physical network (a MAC address each, or shared MAC with L2/L3 modes).

Legacy applications that expect to be on the LAN

A host with two user-defined bridge networks and the embedded DNS resolver and a published port forwarded from the host interface to one container

User-defined bridges and DNS

docker network create backend
docker run -d --name orders-db --network backend -e POSTGRES_PASSWORD=dev-only-password postgres:18.1
docker run -d --name orders --network backend --network-alias api \
  -e SPRING_DATASOURCE_URL=jdbc:postgresql://orders-db:5432/postgres orders-service:1.4.0
docker run --rm --network backend busybox:1.37 nslookup orders-db     # resolved by Docker's embedded DNS (127.0.0.11)
docker network create frontend
docker network connect frontend orders       # a container can join several networks
docker network inspect backend               # subnet, gateway, attached containers
docker network ls
docker rm -f orders orders-db                # a network with attached containers cannot be removed
docker network rm backend frontend

Derived from Bridge network driver and docker network. The embedded DNS server at 127.0.0.11 answers for container names and aliases on the networks a container is attached to and forwards everything else to the host’s resolvers. Containers on different user-defined networks cannot reach each other unless one is connected to both — which is how you separate a public-facing tier from a database tier.

Publishing ports

A container’s ports are reachable from other containers on the same network without any configuration. To make a port reachable from the host or other machines, publish it:

Flag Effect

-p 8080:80

Host port 8080 (all host interfaces, IPv4 and IPv6) to container port 80/tcp

-p 127.0.0.1:8080:80

Only reachable from the host itself

-p 8080:80/udp

UDP instead of TCP

-p 80

Container port 80 to a random free host port

-P

Every port declared with EXPOSE, each to a random host port

docker run -d --name web -p 127.0.0.1:8080:80 nginx:1.29
docker port web                     # 80/tcp -> 127.0.0.1:8080
curl -s http://127.0.0.1:8080 >/dev/null && echo reachable

Derived from Port publishing and mapping. Two points the official page stresses:

  • Publishing is insecure by default: -p 8080:80 listens on every host interface, so the port is reachable from the network, not just from your machine. Bind development databases and debug ports to 127.0.0.1.

  • EXPOSE is documentation: it records which ports the image listens on and feeds -P, but publishes nothing by itself.

sequenceDiagram participant C as Client 203.0.113.7 participant H as Host eth0 :8080 participant F as iptables or nftables DNAT participant B as Bridge br-backend participant W as Container web 172.18.0.2:80 C->>H: TCP SYN to host:8080 H->>F: nat PREROUTING / DOCKER chain F->>B: rewrite destination to 172.18.0.2:80 B->>W: forward packet W-->>B: reply B-->>F: reverse NAT F-->>C: reply appears to come from host:8080

Reaching the host from a container

A container sometimes needs a service running on the host — an IDE-run application that a container calls back, or Prometheus scraping a locally run service. Docker Desktop provides the name host.docker.internal; on Linux the same name is created with the special host-gateway value:

docker run --rm --add-host=host.docker.internal:host-gateway curlimages/curl:8.16.0 \
  -s http://host.docker.internal:8080/actuator/health

In Compose, the equivalent is extra_hosts: ["host.docker.internal:host-gateway"]. Derived from --add-host and Docker Desktop networking.

DNS and IPv6

  • Containers on the default bridge inherit the host’s /etc/resolv.conf (minus localhost resolvers); containers on user-defined networks use the embedded DNS server. Override with --dns, --dns-search and --dns-option, or daemon-wide in daemon.json.

  • IPv6 is opt-in per network: docker network create --ipv6 --subnet 2001:db8:1::/64 v6net. Without it, ports published on the host’s IPv6 addresses are proxied to the container’s IPv4 address.

Derived from DNS services and Use IPv6 networking.

Packet filtering and firewalls

On Linux, Docker programs the kernel’s packet filter to implement NAT, port publishing and network isolation. Two backends exist:

  • iptables (the default): Docker creates its own chains (DOCKER, DOCKER-FORWARD, DOCKER-INGRESS, …​) and a DOCKER-USER chain that it never modifies, where you add rules that must apply to container traffic — for example restricting a published port to a source range:

    sudo iptables -I DOCKER-USER -i eth0 ! -s 192.0.2.0/24 -j DROP
  • nftables (experimental since Engine 29.0): enabled with "firewall-backend": "nftables" in daemon.json. Docker creates its own nftables tables; there is no DOCKER-USER chain, so custom rules go into your own tables with the right priority. It does not enable IP forwarding for you — the daemon refuses to start bridge networks that need it until you enable it (or set --ip-forward=false).

The consequence both backends share, and the most common surprise: published ports bypass host firewalls such as ufw and firewalld’s default zones, because Docker’s NAT happens before those rules are evaluated. Restrict exposure by binding to 127.0.0.1, by DOCKER-USER rules, or by not publishing at all. Derived from Packet filtering and firewalls, Docker with iptables and Docker with nftables.

Proxies

Behind a corporate proxy, three different things may need proxy settings: the daemon pulling images (daemon.json "proxies" or systemd environment), the build (--build-arg HTTP_PROXY=…​, which BuildKit treats as a predefined argument that does not invalidate the cache), and the containers (the CLI injects HTTP_PROXY, HTTPS_PROXY and NO_PROXY from ~/.docker/config.json "proxies"). See Daemon proxy configuration and Use a proxy server with the Docker CLI.

Learning Docker (chapter 8) connects containers with --link db:db, which added a hosts entry and injected DB_PORT_5432_TCP_ADDR-style environment variables. Links only ever worked on the default bridge, are deprecated, and since Engine 29 the environment variables are no longer injected. Use a user-defined network and connect by container name or --network-alias instead, as shown above. See Legacy container links.

Likewise, the chapter 6 picture of docker0 plus NAT describes only the default bridge. It is still how the default network works, but user-defined bridges (one Linux bridge per network, named br-<id>) are what Compose and every example in this section use.