Docker relies heavily on Netfilter and iptables in the Linux kernel to enforce network isolation, prevent unauthorized external access, and manage routing between the public internet and internal container networks.
When the Docker daemon starts, it bypasses standard Linux firewall paradigms (like UFW or Firewalld) and injects its own custom chains into the kernel's nat and filter tables.
Here is a senior-level look at how iptables achieves this isolation under the hood:
1. The Core Architecture: Custom Chains
Instead of dumping all rules into the default INPUT or FORWARD chains, Docker structures its firewall logic into three primary custom chains within the filter table:
DOCKER: Contains the rules that map public-facing ports to internal container endpoints (populated dynamically via-por--publish).DOCKER-USER: A blank, pre-routed chain reserved exclusively for user-defined firewall rules. This is the only place engineers should manually append rules to block or allow traffic before Docker's automated logic takes over.DOCKER-ISOLATION-STAGE-1&STAGE-2: Chains specifically designed to block layer-3 routing between different user-defined Docker bridge networks.
2. Restricting Public Internet to Containers (Ingress Isolation)
By default, any container attached to a bridge network is invisible to the public internet unless explicitly published.
[ Public Internet Traffic ]
│
▼
[ filter: FORWARD ]
│
[ DOCKER-USER Chain ] <─── Best place to drop malicious IPs
│
[ DOCKER-ISOLATION ]
│
[ DOCKER Chain ] <─── Evaluates published ports (-p)
/ \
(Match Found) (No Match / Drop)
/ \
▼ ▼
[ DNAT to Container ] [ DROP / REJECT ]
The Default DROP Policy: Docker configures the main kernel
FORWARDchain to pass evaluation down into theDOCKERchain. If an incoming packet from the public internet attempts to access a container's internal IP address (e.g.,172.17.0.3), and it did not arrive through an explicitly published port mapped in theDOCKERchain, it hits a catch-all rule that silently drops or rejects the traffic.The Connection Tracking (
conntrack) Loophole: To allow containers to download packages or hit external APIs without letting the internet hit them back, Docker inserts a stateful tracking rule:bash
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPTAnvänd koden med försiktighet.
This ensures that only response packets belonging to connections originally initiated by the container are permitted back through the host firewall.
3. Inter-Network Isolation (Preventing Cross-Bridge Contamination)
If you create two separate user-defined bridge networks (network_A and network_B), they are both attached to the same host kernel. Technically, the host's primary routing table knows how to route traffic between them. Docker uses the DOCKER-ISOLATION chains to explicitly break this capability.
Stage 1 Evaluation: Every packet entering the
FORWARDchain is tested againstDOCKER-ISOLATION-STAGE-1. If a packet originates from virtual interfacebr-network-Aand is destined for virtual interfacebr-network-B, it is instantly handed off toSTAGE-2.Stage 2 Dropping:
STAGE-2evaluates the target interface. If the packet is indeed trying to cross intobr-network-B, it hits a hard drop rule:bash
-A DOCKER-ISOLATION-STAGE-2 -o br-network-B -j DROPAnvänd koden med försiktighet.
This completely isolates multi-tenant workloads or staging/production environments co-existing on the same bare-metal host interface.
4. Outbound Hiding: Masquerading (Egress Isolation)
Containers use private IP spaces (typically 172.17.0.0/16 or similar RFC 1918 blocks) that are completely unroutable on the public internet. To allow containers to talk to external networks, Docker utilizes Source Network Address Translation (SNAT) via the MASQUERADE target.
In the nat table, Docker appends:
bash
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
Använd koden med försiktighet.
How it works: When a container packet leaves the host bound for an external server (e.g.,
8.8.8.8), the host kernel strips out the container's private source IP (172.17.0.3) and replaces it with the host's public/external physical interface IP.The kernel maps this temporary session in its memory state table. When the external server responds to the host, the host safely translates it back to the respective container namespace.
⚠️ The "Gotcha": The UFW / Firewalld Conflict
A common architectural pitfall occurs when engineers install a tool like UFW (Uncomplicated Firewall) on the host system and write a rule like ufw deny proto tcp from any to any port 8080.
They assume port 8080 is now secure from the public internet. It is not.
Why it fails: UFW operates primarily on the standard
INPUTchain. However, because Docker uses Destination NAT (DNAT) to route published container ports, the Linux kernel processes the packet via theFORWARDchain instead ofINPUT.Because Docker's
iptablesrules are evaluated before UFW's high-level abstractions in the forward path, Docker completely bypasses UFW restrictions.The Fix: To block public access to a container while maintaining internal access, you must either append the block directly into the
DOCKER-USERchain, or explicitly bind the port to the host's loopback interface during initialization:docker run -p 127.0.0.1:8080:8080.