PID 1 Problem (Container Init Process)
In plain English
Plain definition
The PID 1 problem occurs when an application running as a container's first process does not behave like an init system. If it fails to forward termination signals or reap orphaned children, deployments can hang, require forced kills and accumulate zombie processes; exec-form commands or a minimal init fix the lifecycle.
The Linux kernel treats PID 1 specially: any signal sent to it that lacks an explicit handler is ignored by default, rather than applying the standard default action (terminate, core dump, etc.). This exception exists because traditional init systems (systemd, sysvinit) must never die accidentally. When a container runtime creates a new PID namespace, whatever process you designate as the entrypoint becomes PID 1 in that namespace and inherits this exact semantic—regardless of whether it was ever designed to be an init system.
Most application binaries, shell scripts, and interpreted runtimes (Node.js, Python, Java) do not install a SIGTERM handler and do not call wait()/waitpid() on orphaned descendants. Two failure modes result: (1) zombie accumulation—child processes that exit are reparented to PID 1 and never reaped, eventually exhausting the PID namespace or leaking file descriptors; (2) signal black-holing—when CMD ["sh", "-c", "node server.js"] is used, the shell becomes PID 1 and, in many shells, does not forward SIGTERM to its child process tree at all, so the actual application never sees the termination request.
This directly collides with Kubernetes’ pod termination lifecycle: kubelet sends SIGTERM to the container’s PID 1, waits terminationGracePeriodSeconds (default 30s), then sends SIGKILL. If PID 1 ignores or fails to propagate SIGTERM, every pod termination silently degrades into a hard SIGKILL after the full grace period—dropping in-flight connections, aborting transactions, and inflating shutdown latency across rolling deployments. Sidecar-heavy pods and multi-process containers amplify this because orphan reparenting and signal fan-out both scale with process tree depth.
The standard mitigation is inserting a minimal init shim—tini, dumb-init, or Docker’s built-in --init flag (which wraps tini)—as PID 1. These shims correctly reap zombies via a SIGCHLD loop and forward received signals to the real application process group. Heavier alternatives like s6-overlay add supervised multi-process management for images intentionally running several daemons. Statically compiled binaries (Go, Rust) that explicitly register signal handlers and have no subprocesses can safely run directly as PID 1 without a wrapper, since they satisfy the reaping/signal contract themselves.