Skip to content

Docker Networking

  • Containers are not exposed to the outside world by default. Each container gets its own network namespace with a private IP address that is only reachable within Docker’s virtual network.
  • To expose a container to the host or internet, you must explicitly publish ports: docker run -p 8080:80 nginx

Docker networking is built on three layered components: the Container Network Model (design spec) → Libnetwork (control plane implementation) → Drivers (data plane for each topology).

Network scopes define the reach of a Docker network:

  • local - confined to a single host. Containers on other hosts cannot join or route into it.
  • global - provisioned on every Swarm node, but traffic is not routed between nodes automatically.
  • swarm - spans all Swarm hosts with transparent cross-host routing. Used by overlay networks.
Docker Networking

The CNM is the formal design specification for Docker networking. It defines three constructs that map directly to Linux primitives:

CNM ConceptLinux PrimitiveRole
SandboxNetwork namespaceIsolated network stack per container (interfaces, routing table, iptables, DNS config)
Endpointveth pairVirtual cable connecting a sandbox to a network. One endpoint per network the container joins.
NetworkLinux bridge (docker0)Groups endpoints for communication; isolates them from other networks
Docker CNM

Libnetwork is the open-source control plane implementation of the CNM (originally embedded in the Docker daemon, later extracted to reduce coupling). Drivers implement the data plane - each driver type realizes the CNM constructs for a specific topology (bridge, overlay, macvlan).

Libnetwork Drivers
  • Libnetwork implements the control plane, but it relies on drivers to implement the data plane.

Docker ships with several built-in network drivers:

DriverUse Case
bridgeDefault. Containers on the same host communicate via a virtual bridge. Isolated from host network.
hostContainer shares the host’s network stack directly. No port mapping needed. Loses network isolation.
noneDisables all networking. Container has only a loopback interface.
overlayMulti-host networking for Docker Swarm. Creates a virtual network spanning multiple Docker hosts.
macvlanAssigns a real MAC address to the container. Container appears as a physical device on the LAN.
ipvlanLike macvlan but shares the host’s MAC. Useful where MAC proliferation is a problem.

A bridge is an interface that connects multiple networks so that they can function as a single network. Bridges work by selectively forwarding traffic between the connected networks based on another type of network address.

  • It uses Linux namespaces, virtual Ethernet devices, and the Linux firewall to build a specific and customizable virtual network topology.

  • The default bridge is named bridge, but it’s recommended to create user-defined bridge networks for better isolation and DNS resolution.

  • Docker automatically registers container names with an internal DNS service and allows containers on the same network to find each other by name. The exception to this rule is the built-in bridge network that does not support DNS resolution.

    Terminal window
    # Containers on the default bridge can reach each other by IP, NOT by name
    docker run -d --name app1 nginx
    docker run -d --name app2 nginx
    # User-defined bridge networks support DNS resolution by name
    docker network create my-net
    docker run -d --name app1 --network my-net nginx
    docker run -d --name app2 --network my-net nginx
    # Now app2 can ping app1 by name: ping app1
  • Always use user-defined bridge networks in production. The default bridge lacks DNS name resolution between containers.

Terminal window
# Container uses host networking - no port mapping
docker run -d --network host nginx
# nginx is now accessible on host port 80 directly
  • Useful for performance-sensitive applications or when the container needs to manage host interfaces.
  • Instructs Docker not to create any special networking namespace or resources for attached containers. Containers on the host network interact with the host’s network. Whatever software is running inside the resulting container will have the same degree of access to the host network as it would running outside the container.
  • Not available on Docker Desktop (macOS/Windows) - the VM boundary prevents direct host network access.

The none driver gives a container a completely isolated network namespace with no external interfaces - only a loopback (127.0.0.1). The container cannot reach or be reached from anywhere outside itself.

  • Principle of least privilege: If a workload doesn’t need network access, it should have none. Unnecessary network exposure is an attack surface.

  • Sensitive operations: The none network is ideal for tasks where network isolation must be guaranteed - e.g., generating a random password or encryption key. Any data exfiltration over the network is physically impossible.

  • Foundation for external orchestration: Containers on none can have custom network interfaces added by third-party orchestrators (e.g., Kubernetes CNI plugins) after startup, without Docker’s networking being involved at all.

    Terminal window
    docker run --rm --network none alpine sh -c "echo 'no network, no risk'"

MACVLAN assigns a real MAC address and IP address from an external physical network to each container, making them appear as distinct physical servers or VMs on the LAN. The underlying network has no awareness of Docker - it sees normal MAC/IP pairs.

  • Use case: Partially containerized environments where containers must communicate directly with traditional VMs or physical servers on an existing VLAN.

  • Sub-interfaces: The driver creates Linux sub-interfaces (e.g., eth0.100) tagged with 802.1q VLAN IDs.

  • VLAN trunking: A single host can run multiple MACVLAN networks, each mapped to a different underlying VLAN.

    Terminal window
    # Create a MACVLAN network mapped to VLAN 100
    docker network create \
    -d macvlan \
    --subnet 10.0.0.0/24 \
    --gateway 10.0.0.1 \
    --ip-range 10.0.0.128/25 \
    -o parent=eth0.100 \
    my-macvlan-net
  • Always use --ip-range to carve out a reserved block of IPs for Docker. The MACVLAN driver has no ARP-based collision detection - without it, Docker may assign an IP already in use on the physical network.

Bridge Network

Under the hood, Docker uses three Linux primitives:

  • Each container gets its own network namespace with its own interfaces, IP address, routing table, and iptables rules.
  • The namespace is created by the Docker daemon when the container starts.
  • Docker creates a veth pair - two virtual interfaces linked together at the kernel level.

  • One end goes inside the container namespace (appears as eth0 inside the container).

  • The other end attaches to a virtual bridge on the host (docker0 for the default bridge).

  • Traffic flows: container → veth → bridge → host/internet.

    Terminal window
    # See the docker0 bridge and its connected veth interfaces
    ip addr show docker0
    ip link show | grep veth
  • Docker manages iptables rules on the host to handle port publishing, NAT, and inter-container firewall policies.
  • docker run -p 8080:80 adds an iptables DNAT rule: traffic arriving on host port 8080 gets forwarded to the container’s port 80.
  • nftables gotcha (Debian 12+, RHEL 9+): These distros default to nftables. Docker still writes iptables rules (via iptables-nft compatibility layer). If you mix raw nft rules with Docker, verify rules aren’t silently dropped. Check with iptables -L -n and nft list ruleset to see both views.
Terminal window
# List networks
docker network ls
# Create a custom bridge network
docker network create my-net
# Create with a specific subnet
docker network create --driver bridge --subnet 192.168.50.0/24 my-net
# Allow already-running containers to join/leave dynamically at any time
docker network create --attachable my-net
# Attach metadata labels for easier querying and infrastructure management
docker network create --label project=web --label env=prod my-net
# Full example: subnet, reserved IP range, and attachable
docker network create \
--driver bridge \
--subnet 10.0.42.0/24 \
--ip-range 10.0.42.128/25 \
--attachable \
my-net
# Connect a running container to a network
docker network connect my-net my-container
# Disconnect a container from a network
docker network disconnect my-net my-container
# Inspect network details (connected containers, IPs, etc.)
docker network inspect my-net
# Remove a specific network (fails if containers are still connected)
docker network rm my-net
# Remove all unused networks
docker network prune
Port Publishing
Terminal window
# Publish a single port: host:container
docker run -p 8080:80 nginx
# Publish on a specific host interface
docker run -p 127.0.0.1:8080:80 nginx # Only accessible from localhost
# Publish all EXPOSE'd ports to random host ports
docker run -P nginx
# View published ports for a container
docker port my-nginx
Port Publishing
  • Every user-defined network includes an embedded DNS server at 127.0.0.11. All containers are pre-configured to use it.
  • When a container is created with --name or --net-alias, Docker automatically registers that name and its current IP with the embedded DNS.

Resolution flow (container A resolves container B by name):

  1. A’s local resolver checks its local cache
  2. Cache miss → recursive query sent to 127.0.0.11
  3. Embedded DNS looks up the name-to-IP mapping
  4. Returns B’s IP address (only if both are on the same Docker network)
  5. A sends traffic directly to B’s IP

Rules:

  • Network-scoped: DNS resolution only works between containers on the same network. Different networks cannot resolve each other’s names.
  • Default bridge exception: The default bridge network does not support DNS name resolution - only user-defined networks do.
  • --net-alias: Assigns an additional DNS alias to a container. Multiple containers can share the same alias, enabling simple round-robin DNS across them.

Docker provides four flags to customize a container’s name resolution stack at creation time:

--hostname - sets the container’s internal identity (added to its own DNS override):

Terminal window
docker run --hostname web-server-1 my-app
# Container can resolve its own name to its IP, but neighbors don't auto-learn this name

Useful for applications that must self-identify. Neighboring containers do not automatically discover this hostname unless paired with an external DNS server.

--dns - specifies external DNS servers. Can be passed multiple times to add backup servers:

Terminal window
# Primary + fallback DNS
docker run --dns 8.8.8.8 --dns 1.1.1.1 my-app

Engine-level defaults can be set in /etc/docker/daemon.json - but already-running containers retain old settings until restarted.

--dns-search - sets a default domain suffix appended to unqualified hostnames. Writes to /etc/resolv.conf:

Terminal window
# Resolve "myservice" → "myservice.dev.mycompany.com"
docker run --dns-search dev.mycompany.com my-app
# Swap to test environment without code changes
docker run --dns-search test.mycompany.com my-app

This is the recommended pattern for environment agnosticism - applications look up short names (e.g., myservice) and Docker resolves the full environment-specific FQDN automatically.

--add-host - manually maps a hostname to an IP by writing directly to /etc/hosts. Most granular option:

Terminal window
# Map "db" to a specific IP inside the container
docker run --add-host db:10.0.0.5 my-app
# Block a domain by routing it to loopback (useful for ad/tracking blocking)
docker run --add-host analytics.badsite.com:127.0.0.1 my-app
# Force traffic through a local proxy or SSH tunnel
docker run --add-host api.example.com:127.0.0.1 my-app

Unlike --dns and --dns-search, --add-host cannot be configured as an engine-level default - it must be set per container.

Multihost Networking

When containers must communicate across different physical hosts, two approaches are available:

ApproachDriversHow it worksLimitation
Underlaymacvlan, ipvlanAssigns routable IPs directly to containers - appear as physical nodes on the LANHost-network dependent, rarely portable, blocked on managed clouds
OverlayoverlayCluster-aware virtual bridge spanning all Swarm nodesContainers not routable externally without port publishing

Overlay networks are the standard choice for Swarm deployments - definitions are portable and independent of the underlying host network.

In Docker Swarm, services can be published to external clients in two modes:

ModeAccessible viaTrade-off
Ingress (default)Any swarm node, including those without a replicaHigh availability - routing mesh distributes traffic
HostOnly nodes actively running a replicaHigher throughput, but no automatic failover

How ingress mode works:

  1. An external client hits any swarm node on the published port
  2. The node forwards traffic to the ingress network (a swarm-wide overlay created automatically on docker swarm init)
  3. The ingress network routes to a node running a service replica
  4. Swarm load-balances across all healthy replicas automatically

Publishing in host mode requires the long-form syntax - the short form always uses ingress:

Terminal window
# Ingress mode (default - short form)
docker service create -p 5005:80 nginx
# Host mode - long-form syntax required
docker service create \
--publish published=5005,target=80,mode=host \
nginx
Terminal window
# Two containers sharing the same network namespace (used in service mesh sidecars)
docker run -d --name app my-app
docker run -d --network container:app \
--name sidecar envoy:latest
# "sidecar" sees the same network interfaces, IP, and ports as "app"

This is how Envoy/Istio sidecars intercept traffic without modifying the application container.

For complex infrastructures requiring custom service discovery, load balancing, or CNI plugins beyond Docker’s native capabilities (e.g., Kubernetes):

  1. Deploy containers on the none network - Docker provisions only the isolated network namespace
  2. A third-party orchestrator (e.g., Kubernetes + a CNI plugin) takes over - creating virtual interfaces, registering with service discovery, and managing port publishing
Terminal window
# Start a container with no networking - hand namespace to external orchestrator
docker run -d --network none my-app

The Docker daemon log location depends on the OS init system:

OS / InitLocation
Linux (systemd)journalctl -u docker.service
Linux (upstart, Ubuntu)/var/log/upstart/docker.log
Linux (RHEL / rpm-based)/var/log/messages
Linux (Debian, others)/var/log/daemon.log
WindowsWindows Event Viewer or ~\AppData\Local\Docker

To increase verbosity, edit /etc/docker/daemon.json and restart Docker:

{
"debug": true,
"log-level": "debug"
}

Log levels: debug (most verbose) → info (default) → warnerrorfatal.

Container logging relies on the application running as PID 1 writing to STDOUT/STDERR. The configured log driver captures and forwards that output.

Terminal window
# View logs for a container
docker logs my-app
# Follow logs from a Swarm service
docker service logs my-service
Log DriverNotes
json-fileDefault. Compatible with docker logs. Stored on host filesystem.
journaldRoutes to systemd journal. Compatible with docker logs.
syslog, splunk, awslogs, etc.Platform-specific. Not compatible with docker logs - must use native tooling.

Set a default log driver in /etc/docker/daemon.json:

{
"log-driver": "journald"
}

Override per container or service at runtime:

Terminal window
docker run --log-driver journald --log-opt tag="my-app" my-app:latest