Idunworks Issue #01

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 -p or --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 FORWARD chain to pass evaluation down into the DOCKER chain. 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 the DOCKER chain, 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 ACCEPT
    

    Anvä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 FORWARD chain is tested against DOCKER-ISOLATION-STAGE-1. If a packet originates from virtual interface br-network-A and is destined for virtual interface br-network-B, it is instantly handed off to STAGE-2.

  • Stage 2 Dropping: STAGE-2 evaluates the target interface. If the packet is indeed trying to cross into br-network-B, it hits a hard drop rule:

    bash

    -A DOCKER-ISOLATION-STAGE-2 -o br-network-B -j DROP
    

    Anvä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 INPUT chain. However, because Docker uses Destination NAT (DNAT) to route published container ports, the Linux kernel processes the packet via the FORWARD chain instead of INPUT.

  • Because Docker's iptables rules 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-USER chain, or explicitly bind the port to the host's loopback interface during initialization: docker run -p 127.0.0.1:8080:8080.

Log in to like this article, or create an account .
0 reads

Comments

Log in to post comments. Log in
No comments yet. Be the first to comment.