Under the Hood #5 How Linux Networking Really Works Before Docker and Kubernetes Take Over
Understand how Linux networking works before Docker and Kubernetes take over. Follow the journey of a packet through NIC, routing, Netfilter, conntrack, sockets, and container networking to troubleshoot real-world Kubernetes and cloud-native network issues.
In the previous article of the Under the Hood series, we explored how cgroups v2 isolate CPU, memory, and I/O resources to make containers possible. We saw that Docker and Kubernetes don't magically control system resources themselves; they rely on Linux to do the heavy lifting.
But once a container is running, another question naturally follows.
How does a container actually communicate with the outside world?
Every time you open a website, call an API, connect to a database, or access a Kubernetes Service, packets travel across a network. Whether they're coming from another Pod, another virtual machine, or a user's browser, those packets all follow the same journey before your application ever sees them.
Most engineers work with Docker, Kubernetes, Ingress controllers, or Service manifests. These tools make networking feel simple: create a Service, expose a port, and everything works until it doesn't.
When a Pod can't reach another Pod or an API starts timing out, experienced engineers don't immediately start editing Kubernetes manifests. They ask a simpler question:
"Where is the packet?"
Before Docker forwards traffic, before Kubernetes performs service discovery, and before your application receives a request, Linux has already processed every single packet that enters the machine.
The Common Misconception
Ask someone how a request reaches a container running in Kubernetes, and you'll often hear an explanation that looks like this:
Internet
│
▼
Docker
│
▼
Kubernetes
│
▼
Application
It feels logical because Docker creates containers and Kubernetes manages them. However, neither Docker nor Kubernetes receives the packet first.
Long before either becomes involved, Linux accepts the packet from the network card, examines its destination, checks routing rules, consults the firewall, tracks the connection, and decides which process should receive it.
The actual journey looks like this:
Internet
│
▼
Network Interface Card (NIC)
│
▼
Linux Kernel
│
▼
Routing
│
▼
Firewall (Netfilter)
│
▼
Connection Tracking (conntrack)
│
▼
Socket
│
▼
Docker / Kubernetes
│
▼
Application
Docker and Kubernetes do not replace Linux networking; they build on top of capabilities that Linux has provided for decades.
1. A Packet Arrives (NIC & DMA)
When an HTTP request reaches your server, it first hits the Network Interface Card (NIC), the physical or virtual network adapter attached to the machine.
As soon as the packet arrives, the NIC transfers it directly into system memory (RAM) using Direct Memory Access (DMA) without taxing the CPU. Once the packet is in memory, the NIC notifies the Linux kernel that new data has arrived.
At this stage, the kernel does not know whether the packet is intended for NGINX, a Kubernetes Pod, or an SSH session. Its first task is to answer:
"Where is this packet supposed to go?"
2. Routing: How Linux Decides Where the Packet Goes
To determine the destination, Linux examines the packet header (destination IP, protocol, and port) and consults the routing table.
You can view the host routing table using:
Bash
ip route
Example output:
default via 192.168.1.1 dev eth0
10.244.0.0/16 dev cni0
192.168.1.0/24 dev eth0
- Local network traffic: Linux sends it directly through the designated interface (e.g.,
eth0). - Pod network traffic: Linux forwards it through the Container Network Interface (e.g.,
cni0). - External traffic: Linux routes it to the
defaultgateway.
If Linux sends packets to the wrong destination due to a misconfigured route, traffic drops before reaching your application.
3. Netfilter: Linux's Traffic Controller
Once Linux determines the path, the packet enters Netfilter, the framework behind tools like iptables and nftables.
Packet Arrives ──► Routing Decision ──► Netfilter ──► [ Accept | Drop | Reject | Modify ]
Netfilter does far more than block malicious traffic:
- Port Translation (NAT): Maps host ports to container ports (e.g., host port 8080 to container port 80).
- Traffic Steering: Kubernetes components like
kube-proxywrite Netfilter rules to balance and route Service traffic directly to Pods.
4. conntrack: How Linux Remembers Connections
Linux tracks stateful conversations through Connection Tracking (conntrack).
When a new connection starts, conntrack creates an entry in a connection table. Subsequent packets belonging to the same TCP conversation skip redundant rule evaluations.
New Packet ──► conntrack Creates Entry ──► Connection Established ──► Future Packets Reuse Entry
If the conntrack table becomes full or corrupted, new incoming connections drop immediately even if CPU, memory, and disk utilization look normal.
5. Sockets: Where the Packet Meets Your Application
After passing through routing, Netfilter, and connection tracking, the kernel determines which application owns the target port by referencing sockets.
When an application (like NGINX or PostgreSQL) starts, it asks the kernel to open a socket on a specific port. When a packet arrives, Linux copies the data into that socket's receive buffer and alerts the application process.
To see which applications are currently bound to sockets, run:
Bash
ss -tulpn
If an application crashes or fails to bind to its port, the socket won't exist, and Linux will reject the incoming packet with an error like Connection Refused.
Where Docker & Kubernetes Join
Neither Docker nor Kubernetes changes how the kernel handles packets:
Docker
When you run a container, Docker sets up isolation primitives:
- Creates a network namespace (giving the container its own isolated network stack view).
- Creates a virtual Ethernet pair (
veth) acting as a virtual cable. - Connects the host side of the
vethpair to a virtual bridge (docker0).
Internet ──► Linux Kernel ──► docker0 Bridge ──► veth Pair ──► Container Socket
Linux does all the actual packet forwarding across these virtual interfaces.
Kubernetes
Kubernetes extends this cross-node model using a Container Network Interface (CNI) plugin (e.g., Calico, Cilium, Flannel):
- Each Pod gets its own network namespace and IP.
kube-proxy(or an eBPF replacement) programs host-level Netfilter/routing rules to seamlessly redirect Service IPs to underlying Pod IPs.
Real Production Investigation Workflow
When Pods are unresponsive but metrics look normal, follow the packet step-by-step through Linux instead of guessing:
Check if the connection tracking table is saturated:Bash
conntrack -L
Check active firewall rules for accidental drops:Bash
iptables -L -n -v
# or
nft list ruleset
Inspect live traffic on the wire to see if packets arrive or drop:Bash
tcpdump -i eth0 port 80 -n
Confirm the application is actively listening on its socket:Bash
ss -tulpn
Check the host routing table for missing or incorrect routes:Bash
ip route
Verify interfaces exist and are UP:Bash
ip addr
Key Takeaways
- Linux handles the packet first: Docker and Kubernetes automate networking setup, but the Linux kernel does the actual work.
- Core Flow:
NIC → Routing → Netfilter → conntrack → Socket → Container. - Troubleshoot systematically: Trace the packet layer-by-layer through the kernel when debugging container networking issues.
Conclusion
Networking often feels mysterious because modern platforms hide most of its complexity behind abstractions. Docker gives containers their own networks. Kubernetes lets Pods communicate across entire clusters. Cloud providers create virtual networks that span regions.
It's easy to forget that none of these technologies replace Linux.
Every packet still enters through a network interface. Every routing decision is still made by the kernel. Every firewall rule, every tracked connection, and every socket ultimately belongs to Linux.
Once you understand that journey, networking problems become much less intimidating. Instead of immediately inspecting Kubernetes manifests or restarting containers, you can trace the packet through the operating system and identify exactly where communication stops.
The next time a Pod can't reach another Pod, don't begin with Kubernetes.
Start with Linux.
More often than not, the answer is already waiting somewhere inside the kernel.
Next in the Under the Hood Series
Under the Hood #6: Zombie Processes Explained: Why Dead Processes Refuse to Leave Linux
Every Linux administrator has seen a process marked as Z, but surprisingly few know what it actually means. If a zombie process has already finished executing, why does it still appear in the process list? Why can't you kill it? And under what circumstances can thousands of zombie processes bring down a production server?
In the next article, we'll explore the complete lifecycle of a Linux process, understand why zombie processes exist, and learn how experienced engineers investigate and resolve them without rebooting a system.