Namespaces, The Isolation Illusion
Two containers on the same host can have a process with PID 1. Both think they're the only thing running. Neither can see the other. How?
In Lesson 1.1 we established that a container is a normal process with restrictions. Namespaces are the first and most visible restriction: they control what a process can see. A namespace wraps a global kernel resource so that the processes inside it get their own private instance of that resource, unaware that any other instance exists.
The illusion in the scenario is exactly this. Each container's main process lives in its own PID namespace, where the kernel renumbers processes so the first one is PID 1 and no processes outside the namespace are visible. Two containers each see a PID 1 because each has its own PID namespace. Neither sees the other because a PID namespace only shows the processes created within it. There is no conflict, because "PID 1" is a per-namespace label, not a global one.
A namespace isolates visibility of a kernel resource, not the resource itself. Two containers with separate PID namespaces still run on the same process scheduler and share the same physical CPU and RAM. The kernel is one; namespaces just give each process a filtered view of it. Capping the actual resource is a different mechanism, cgroups, covered in the next lesson.
The eight namespace types
Linux has eight namespace types today. A container is typically placed in most of them at once. Each isolates one class of global kernel state.
| Namespace | Flag | What it isolates | Why it matters for containers |
|---|---|---|---|
| Mount | CLONE_NEWNS | The mount table and filesystem tree | The container sees the image as /, not the host's filesystem |
| PID | CLONE_NEWPID | Process IDs | The container's main process is PID 1; it cannot see host processes |
| Network | CLONE_NEWNET | Interfaces, IPs, ports, routes, iptables | The container gets its own network stack (the basis of Module 6) |
| UTS | CLONE_NEWUTS | Hostname and domainname | The container can set its own hostname without touching the host |
| IPC | CLONE_NEWIPC | System V IPC, POSIX message queues | Shared-memory segments do not leak between containers |
| User | CLONE_NEWUSER | UID/GID mappings and capabilities | Container root can map to an unprivileged host UID |
| Cgroup | CLONE_NEWCGROUP | The cgroup root the process sees | Hides the host's cgroup layout from the container |
| Time | CLONE_NEWTIME | Boot and monotonic clock offsets | Lets a container present a different uptime (rarely used) |
They are created with three syscalls: clone() (start a new process in new namespaces), unshare() (move the calling process into new namespaces), and setns() (join an existing namespace, which is how nsenter and kubectl exec work).
Proving isolation with unshare and nsenter
You can create and inspect namespaces with standard tools, no runtime required. Create a process in a new UTS namespace and change its hostname without affecting the host:
# New UTS namespace; changing the hostname inside does not touch the host
sudo unshare --uts bash
hostname isolated
hostname
# isolated <- inside the namespace
# In another terminal on the host:
hostname
# host-machine <- the host hostname is unchanged
nsenter does the opposite: it joins a namespace another process is already in. This is how you debug a container from the host (revisited in Lesson 6.4):
# Enter the network namespace of a running container's process (host PID 48213)
sudo nsenter --target 48213 --net ip addr
# Shows the CONTAINER's interfaces (eth0 with the pod IP), not the host's
Where the illusion lives: /proc
Every namespace a process belongs to appears as a symlink under /proc/<pid>/ns/. The number in each symlink is the namespace's inode: two processes with the same inode share that namespace; different inodes mean isolation.
# The container process (host PID 48213) vs your host shell
readlink /proc/48213/ns/pid # pid:[4026532565] <- container PID namespace
readlink /proc/self/ns/pid # pid:[4026531836] <- host PID namespace (different)
# But they might SHARE a namespace they were not isolated in. Compare each:
# same inode = shared with host, different inode = isolated
This is also how you answer "is this container sharing the host network?" If /proc/<pid>/ns/net matches the host's, the container was started with host networking and has no network isolation at all.
Visibility, not resources
Namespace vs cgroup: the two halves of isolation
Namespace
Controls what a process can SEE
cgroup
Controls what a process can USE
Confusing these two is the most common conceptual error in containers. A process can have a PID namespace (cannot see other processes) yet no memory cgroup (can allocate until the host dies). Isolation of view and isolation of consumption are independent, and a secure container needs both.
Common mistakes
- Assuming namespaces limit resources. They do not. A container with every namespace but no cgroup can still exhaust host CPU or RAM.
- Running with host namespaces without realizing it.
--net=host,--pid=host, orhostNetwork: trueremoves that namespace entirely. A host-PID container can see and signal every process on the node; a host-network container shares the node's ports and can reach the metadata endpoint. Check/proc/<pid>/ns/. - Forgetting the user namespace. Without it, root inside the container is the same UID 0 as root on the host, so a mount or device that leaks out runs as real root. The user namespace is what remaps that to a harmless UID.
- Thinking
kubectl execis magic. It issetns()into the target container's namespaces. Same mechanism asnsenter.
Which namespace is the most security-sensitive?
The user namespace. It is the keystone for two opposite reasons. Defensively, it is the mechanism that maps container root (UID 0) to an unprivileged host UID, so a container breakout lands as a nobody rather than as real root; it is the foundation of rootless containers (Lesson 3.5). Offensively, it historically enabled unprivileged users to reach kernel code paths that assume a privileged caller, which produced a long line of local privilege-escalation CVEs. That double-edged nature is why several distributions ship with unprivileged user namespaces restricted by default. The other namespaces isolate one resource each; the user namespace decides who you are on both sides of the boundary.
Summary
Namespaces give a process a private, filtered view of eight classes of global kernel state. A container is placed in most of them, which is why it believes it is alone: its own PID 1, its own network stack, its own filesystem root. They are created with clone, unshare, and setns, and every one is visible as an inode under /proc/<pid>/ns/. Crucially, they isolate visibility, not consumption. The user namespace is the security keystone. The next lesson covers the other half of the boundary: cgroups, which cap what the process can actually use.
A container is started with its own PID, mount, UTS, IPC, and network namespaces, but the operator forgot to set any resource limits. A bug in the app allocates memory in an infinite loop. What happens, and why?
Walk me through each Linux namespace type and what it isolates. Which one is the most security-sensitive and why?