Your containerized application's process count keeps growing over time, even though nothing appears to be leaking connections or memory. What's likely happening, and why is it specific to running in a container?
Short Answer
This is very likely zombie process accumulation caused by the classic "PID 1 problem": the container's main process (PID 1 inside its own PID namespace) is spawning child processes (directly, or via a shell script, or a language runtime that forks) that exit, but PID 1 isn't properly reaping them via wait() — on a normal Linux host, init (PID 1) is specifically designed to reap orphaned zombies, but many application binaries running as a container's PID 1 were never designed to take on that responsibility, since they were written assuming a real init system would handle it.
Detailed Explanation
Every process that exits needs its parent to call wait() on it to fully clean up its process table entry — until that happens, the exited process remains a "zombie," consuming a process table slot but nothing else. On a normal Linux system, init (PID 1) is specifically responsible for reaping any orphaned process whose original parent has already exited, which is a role real init systems are deliberately built for. When a container's PID 1 is just your application binary (not a real init system), it inherits this reaping responsibility by virtue of being PID 1 in its namespace, but most application code was never written with that responsibility in mind.
Symptoms
- Process count inside the container grows steadily over time without an obvious corresponding increase in actual application load.
- Processes are visible in a zombie state (
psshowingZstatus) when inspected. - Eventually the container hits a process/PID limit (
ulimit, or a cgroup pids controller limit), causing new process creation to fail.
Possible Causes
- The application itself (or a script it runs) spawns child processes or subprocesses that exit, but the application (running as PID 1) never calls
wait()on them, since it wasn't written expecting to be PID 1. - A shell script is used as the container's entrypoint, and shell doesn't automatically reap background/orphaned child processes the way a real init system would.
- A language runtime or process manager inside the container spawns worker processes that exit without being properly reaped by the parent.
Investigation Steps
- Confirm the container's actual PID 1 process:
docker top <container>orpsinside the container, checking what's running as PID 1. - Check for zombie processes specifically:
ps aux(or equivalent inside the container) showing entries inZ(zombie) state. - Correlate zombie accumulation with actual application behavior — is the application forking subprocesses (shelling out to another command, spawning worker processes) as part of its normal operation?
- Check the container's process limit configuration (cgroup
pids.max, or the container runtime's configured limit) to understand how much headroom exists before this becomes a hard failure.
Resolution
- Use a minimal init system as PID 1 instead of the application directly — tools like
tiniordumb-initare specifically designed to be a container's PID 1, correctly reaping zombie processes (and correctly forwarding signals to the actual application, a related but separate PID-1 responsibility) without the weight of a full traditional init system. - In Docker specifically, this can often be enabled with a simple flag:
docker run --initautomatically injects a minimal init process (based ontini) as PID 1, running the specified command as its child — a low-effort fix if you're not already using one. - If using a shell script as the entrypoint, ensure it properly execs into the final process (
exec "$@"at the end, rather than running the final command as a plain subprocess) so the actual application becomes PID 1 directly rather than remaining a child of the shell — combined with a proper init wrapper if the application itself spawns further children. - Verify the fix by monitoring process count over an extended period under normal load, confirming it no longer grows unboundedly.
Prevention
- Default to using
tini,dumb-init, or the equivalent runtime flag (docker run --init) for any container whose main process might spawn child processes, rather than only adding it reactively after hitting this problem. - Be deliberate about entrypoint scripts using
execto hand off to the final process, rather than leaving the shell as an intermediate parent. - Monitor process count as a container health metric where relevant, so slow zombie accumulation is caught proactively rather than discovered when the process limit is hit.
Interview Follow-Up Questions
- Why does proper signal forwarding also matter for a container's PID 1, beyond just zombie reaping?
- How would you detect this problem proactively in a production environment, before it causes an actual outage?
- How does Kubernetes' own Pod-level process handling relate to this problem — does it change anything about needing an init wrapper?
Key Takeaways
- The "PID 1 problem" happens because PID 1 inherits the responsibility of reaping orphaned zombie processes, a role most application binaries were never written to fulfill.
- This is specific to containers because a real Linux host's
initis purpose-built for this reaping role, while a container's PID 1 is often just the application itself. - A minimal init wrapper (
tini,dumb-init, ordocker run --init) is the standard, low-effort fix, correctly reaping zombies and forwarding signals. - Ensure shell-script entrypoints use
execto hand off to the final process, avoiding leaving the shell as an unnecessary intermediate parent process.
References
Related Questions
Last updated August 22, 2026 · Last reviewed August 22, 2026