Skip to content
PID Namespace
step 1/5

Reading — step 1 of 5

Read

~3 min readLinux Namespaces

PID Namespace

Here's the secret that demystifies Docker in one sentence: a container is not a thing — it's a process that's being lied to. The Linux kernel tells a handful of coordinated lies (namespaces), enforces some spending limits (cgroups), and swaps the floor out from under the process (rootfs). This course builds all three, and we start with the most vivid lie: the process ID namespace.

The lie

Run ps inside any container:

PID  COMMAND
  1  node server.js
 12  ps

That server believes it is PID 1 — the init process, the first process on the machine, the root of the process tree from the kernel course. Meanwhile the host sees the same process as, say, PID 48231. Neither is lying to you: the kernel maintains both numbers simultaneously, and shows each observer the one that belongs to their PID namespace.

A PID namespace is created with one flag at process creation (clone(CLONE_NEWPID)). The first process born into it becomes PID 1 of that namespace; its children get 2, 3, 4… — a fresh, private numbering. The namespace is a one-way mirror:

  • Inside looking out: nothing. The container can't see, signal, or even express the existence of host processes. kill -9 48000 inside the container targets its 48000 (which doesn't exist), never the host's. Isolation isn't a permission check that could have bugs — the outside processes are simply unaddressable.
  • Outside looking in: everything, renumbered. The host sees container processes as ordinary PIDs and can manage them like any other. Parent namespaces contain child namespaces — so one process genuinely holds several PIDs at once, one per namespace level it's visible in.

Why PID 1 is a loaded gun

Becoming PID 1 sounds cosmetic; it isn't. The kernel treats PID 1 specially, and containers inherit both special rules:

  1. Signals: PID 1 ignores any signal it hasn't installed a handler for — even SIGTERM/SIGKILL-adjacent defaults that would kill a normal process. Consequence: docker stop sends SIGTERM to your PID 1; an app that never installs a handler ignores it, waits out the 10-second grace period, and gets hard-killed. Every "my container takes exactly 10 seconds to stop" mystery is this paragraph.
  2. Zombie reaping: when a process dies, its parent must wait() on it or it lingers as a zombie (kernel course, process lesson!). Orphans get re-parented to PID 1 — of their namespace. If your app is PID 1 and it spawns-and-forgets subprocesses, nobody reaps; zombies accumulate. This is why minimal init shims (tini, docker run --init) exist: a 10KB PID 1 whose whole job is wait()ing.

One more everyday consequence: /proc (which ps reads) shows the processes of your PID namespace — which is why mounting a fresh /proc is part of container setup (mount lesson, next) and why ps in a container shows two processes instead of the host's four hundred.

Your exercise: PID Namespace Simulator

You'll implement the kernel's bookkeeping: processes created in namespaces (nested included), each process holding a PID per visible namespace, queries answering "what does process X see when it looks at process Y?" — the one-way mirror as data structure. Get the two directions right: inner→outer visibility is no answer (not an error — a genuine "that process doesn't exist from here"), outer→inner shows the outer namespace's number for the inner process. When your simulator's answers match the spec, you understand the line ps prints in every container on earth.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…