How isolation works: namespaces & cgroups
The kernel features under every container.
A container is a normal Linux process dressed up by two kernel mechanisms working together. Namespaces control what the process can see; cgroups control what it can use. There is no “container” object in the kernel — runc just starts a process inside a fresh set of namespaces with cgroup limits applied, and that combination is the container. Internalize this and both isolation and, later, its failure modes become obvious.
Namespaces: the private view
Each namespace type isolates one resource. In its own PID namespace a container sees only its own processes, with its entrypoint as PID 1. Its network namespace gives it its own interfaces and port space; its mount namespace, its own filesystem view; the user namespace can even map container root to an unprivileged host UID. You can watch this from both sides — PID 1 inside, a distinct namespace outside.
$ docker run --rm alpine ps -ef # inside: it is PID 1 and sees nothing elsePID USER COMMAND1 root ps -ef$ sudo lsns -t pid | grep -i alpine # on the host: its own dedicated pid namespace
Scenario: deliberately sharing a namespace
Isolation is the default, but sometimes you share a namespace on purpose. --pid=host lets a debugging container see and signal host processes (handy for troubleshooting, dangerous in production). --network=container:web puts a second container in the first’s network namespace so they share localhost — the classic sidecar pattern, and exactly how a pause container works in Kubernetes. Each shared namespace is a deliberate hole in the wall.
# sidecar: the proxy shares the app’s network namespace and reaches it on localhost$ docker run -d --name web nginx:1.27$ docker run --rm --network=container:web nicolaka/netshoot curl -s localhost:80 | head -1<!DOCTYPE html># debugging: see host processes from inside a container (powerful, not for prod)$ docker run --rm --pid=host alpine ps -ef | head
cgroups: the resource ceiling
cgroups cap what a container may consume so one workload cannot take down the node. --memory sets a hard ceiling (exceed it and the kernel OOM-kills the process), --cpus limits CPU share, and --pids-limit caps the process count — a cheap defence against a fork bomb. Without limits, a single container can consume all of a host’s memory or CPU.
$ docker run -d --name app --memory 256m --cpus 1.5 --pids-limit 200 myapp:1.0$ docker stats --no-stream appNAME CPU % MEM USAGE / LIMIT PIDSapp 2.1% 88MiB / 256MiB 37
Scenario: the noisy neighbor and the mystery kill
One unlimited container degrades everything else on the box, or a container keeps dying and nobody knows why. The fix for the first is limits; the diagnosis for the second is in the container’s state — OOMKilled true means it breached its memory cap and the kernel killed it, so either the limit is too low or the app is leaking. That single field turns “it just keeps restarting” into a concrete answer.
$ docker inspect -f '{{.State.OOMKilled}} exit={{.State.ExitCode}}' apptrue exit=137 # 137 = 128 + SIGKILL(9): the kernel OOM-killed it# raise --memory, or fix the leak — the number is not lying