Namespaces¶
A namespace is a kernel feature that gives a group of processes their own isolated view of one global resource, such as the process tree or the network. Namespaces are the isolation half of a container, and understanding them turns "a container is magic" into "a container is a process with its own namespaces".
Track: Core · Interview weight: High
Must-Know Facts¶
| Fact | Value | Verify with |
|---|---|---|
| Count | Eight types: mnt, pid, net, uts, ipc, user, cgroup, time | ls /proc/self/ns/ |
| mnt | Isolates the mount table (each container's own filesystem view) | unshare --mount |
| pid | Isolates process ids; the first process becomes PID 1 | unshare --pid --fork |
| net | Isolates interfaces, routes, ports, firewall | unshare --net |
| uts | Isolates hostname and domain name | unshare --uts |
| ipc | Isolates System V IPC and POSIX message queues | unshare --ipc |
| user | Isolates uid/gid; maps container root to an unprivileged host uid | unshare --user |
| cgroup | Isolates the cgroup root the process sees | unshare --cgroup |
| Identity | A namespace is an inode number under /proc/PID/ns/ | ls -l /proc/self/ns/net |
| List them | Show namespaces and the processes in each | lsns |
| Create | Start a process in new namespaces | unshare |
| Join | Enter the namespaces of a running process | nsenter -t PID |
| Syscalls | clone, unshare, setns create and join namespaces | man 7 namespaces |
The Eight Namespaces¶
Each process belongs to one namespace of every type, exposed as a symlink under /proc/PID/ns/. The target of each link is type:[inode], and that inode number is the namespace's identity: two processes in the same namespace point at the same inode.
ls -l /proc/self/ns/
Output:
lrwxrwxrwx 1 laborant laborant 0 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 laborant laborant 0 ipc -> ipc:[4026531839]
lrwxrwxrwx 1 laborant laborant 0 mnt -> mnt:[4026531841]
lrwxrwxrwx 1 laborant laborant 0 net -> net:[4026531840]
lrwxrwxrwx 1 laborant laborant 0 pid -> pid:[4026531836]
lrwxrwxrwx 1 laborant laborant 0 time -> time:[4026531834]
lrwxrwxrwx 1 laborant laborant 0 user -> user:[4026531837]
lrwxrwxrwx 1 laborant laborant 0 uts -> uts:[4026531838]
The inodes in the 4026531xxx range are the host's initial namespaces, shared by every normal process. A container gets fresh inodes for the types it isolates, and keeps the host's for the rest.
A namespace is an inode, not a process
A namespace exists as long as a process is in it or a file descriptor or bind mount pins it. Comparing the inode numbers behind two processes' /proc/PID/ns/net links is how you tell whether they share a network namespace.
Listing Namespaces¶
lsns reads /proc and groups processes by namespace, showing the type, the number of processes, and the lowest-PID process in each. On a plain host most processes share the initial namespaces started by PID 1.
sudo lsns | head -8
Output:
NS TYPE NPROCS PID USER COMMAND
4026531834 time 105 1 root /sbin/init
4026531835 cgroup 105 1 root /sbin/init
4026531836 pid 105 1 root /sbin/init
4026531837 user 105 1 root /sbin/init
4026531838 uts 94 1 root /sbin/init
4026531839 ipc 105 1 root /sbin/init
4026531840 net 103 1 root /sbin/init
4026531841 mnt 86 1 root /sbin/init
Every type traces back to /sbin/init (PID 1), which is the shared root. When a container runs, new rows appear with a different NS inode and the container's own process count.
Creating a Namespace with unshare¶
unshare runs a command in new namespaces of the types requested. A new UTS namespace, for example, gives the process its own hostname, so a change inside it does not affect the host.
sudo unshare --uts sh -c 'hostname container01; echo inside: $(hostname)'
echo outside: $(hostname)
Output:
inside: container01
outside: web
The hostname container01 exists only inside the namespace. This is the same mechanism podman run --hostname uses.
The PID Namespace¶
A new PID namespace renumbers processes from 1, so the first process in it becomes PID 1 and sees only its own descendants. --fork makes unshare fork the command (PID 1 must be a child, not unshare itself), and --mount-proc remounts /proc so ps reflects the new namespace.
sudo unshare --pid --fork --mount-proc bash -c \
'sleep 30 & echo "shell pid: $$"; ps -e -o pid,ppid,comm'
Output:
shell pid: 1
PID PPID COMMAND
1 0 bash
2 1 sleep
3 1 ps
Inside, bash is PID 1 with sleep and ps as its children, and its PPID is 0 because its real parent lives outside the namespace and is not visible. This is why the process inside a container that shows as PID 1 is an ordinary host process with a different PID on the host.
PID 1 in a namespace inherits init's duties
The PID 1 of a namespace must reap orphaned children and handle signals, or zombies accumulate and docker stop hangs. A shell as PID 1 does neither well, which is why runtimes offer a small init such as --init, covered in Containers vs VMs.
The Network Namespace¶
A new network namespace starts with only a loopback interface, and that interface is down. It has no route to anything until a virtual link is added, which is how a container starts fully isolated on the network.
sudo unshare --net ip -br link # inside the new namespace
ip -br link | head -2 # the host, for comparison
Output:
lo DOWN 00:00:00:00:00:00 <LOOPBACK>
lo UNKNOWN 00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
eth0 UP de:bc:47:xx:xx:xx <BROADCAST,MULTICAST,UP,LOWER_UP>
The isolated namespace sees only lo, and it is DOWN, so nothing works until it is brought up and connected. Container networking connects this namespace to the host with a veth pair, one end in each namespace, described in Overlayfs and Chroot and the networking module.
Joining a Namespace with nsenter¶
nsenter runs a command inside the namespaces of an already running process, selected by PID. This is how a debugger drops into a container's network or mount namespace without the container's own tools.
sudo nsenter -t <PID> -u hostname # -u = enter that process's UTS namespace
Output:
sandbox
The command ran in the target's UTS namespace and saw its hostname, sandbox, not the host's. nsenter -t <PID> -n ss -tlnp is the common form for inspecting a container's listening sockets from the host.
nsenter is the way into a container that has no shell
A distroless or scratch container has no sh for docker exec. nsenter -t <host-pid> -a enters all of its namespaces using the host's binaries, so you can inspect it with tools the image does not contain.
Common Errors¶
unshare: unshare failed: Operation not permitted¶
Cause: creating most namespaces needs CAP_SYS_ADMIN, so an unprivileged user cannot unshare a PID or mount namespace directly.
Fix: run under sudo, or first create a user namespace (unshare --user --map-root-user), which grants capabilities inside it.
nsenter: cannot open /proc/<pid>/ns/net: No such file or directory¶
Cause: the target PID exited, or the wrong PID was used (the container's in-namespace PID instead of its host PID).
Fix: use the host PID from podman inspect --format '{{.State.Pid}}' or ps, and confirm the process is still running.
Interview Checkpoints¶
L1: What is a namespace, and what does it isolate?
Say first: a namespace gives a set of processes their own isolated instance of a global kernel resource, such as the process tree, network stack, or mount table.
Proof: ls /proc/self/ns/ lists the eight types; lsns shows which processes share each one.
Follow-up: which namespace makes rootless containers possible? (the user namespace, by mapping container root to an unprivileged host uid.)
L1: What is the difference between a namespace and a cgroup?
Say first: a namespace controls what a process can see; a cgroup controls how much it can use.
Proof: namespaces isolate the PID, net and mount views; cgroups cap CPU, memory and pids, covered in Cgroups.
Follow-up: which one does docker run --memory use? (a cgroup.)
L2: Create a process that has its own hostname without affecting the host.
Say first: run it in a new UTS namespace.
Proof:
sudo unshare --uts sh -c 'hostname box; hostname'
Follow-up: how do you also give it its own process tree? (add --pid --fork --mount-proc.)
L2: Inspect a running container's listening ports from the host.
Say first: enter its network namespace with nsenter using the container's host PID.
Proof: sudo nsenter -t $(podman inspect -f '{{.State.Pid}}' web) -n ss -tlnp.
Follow-up: why not use the container's own ss? (a minimal image may not contain it; nsenter uses the host's binaries.)
L3: A container process shows as PID 1 inside but the host cannot find PID 1 for it. Explain.
Say first: PID namespaces renumber from 1, so the same process has one PID inside its namespace and a different, higher PID on the host.
Proof: ps inside shows PID 1; podman inspect -f '{{.State.Pid}}' gives the host PID; both refer to one process.
Follow-up: what does kill 1 inside the container do? (signals the container's PID 1, which stops the container, not the host.)
L4: How does a rootless container let 'root' inside map to a normal user outside?
Say first: a user namespace maps uid 0 inside to the caller's unprivileged uid on the host, and a range of further ids to the caller's subuid allocation, so root inside owns nothing privileged outside.
Proof: /proc/PID/uid_map shows the mapping; /etc/subuid holds the range, covered in Podman and Quadlet.
Don't say: that rootless containers run as real root; the container root is confined to the mapped range.
L4: What keeps a namespace alive after the process that created it exits?
Say first: a namespace persists while any process is a member, or while a file descriptor or bind mount pins it, and is destroyed when the last reference goes away.
Proof: ip netns add bind-mounts the net namespace under /var/run/netns so it survives with no process in it.
Don't say: that a namespace always disappears the instant its creator exits.
Related¶
- Cgroups: the resource-limit half of a container
- Overlayfs and Chroot: the filesystem view a mount namespace carries
- Containers vs VMs: why a container is a process, and PID 1 duties
- Podman and Quadlet: rootless containers and the user namespace
- Process Fundamentals: PID, PPID and the process tree
- Round 4 Internals: namespaces, PID 1 and rootless mapping
Captured on Rocky Linux 10.2 on an iximiuz Labs FlexBox microVM, kernel 6.1.167, 2026-09. The host MAC address is scrubbed.