Ch. 13 · Docker

Docker init, PID 1 and Signal Handling

Why the first process in a container must reap children and forward signals, and how to avoid zombies and ignored shutdowns.

~2 min readadvancedupdated Oct 5, 2026

The first process in a container is PID 1 and takes on kernel responsibilities, most importantly reaping orphaned child processes and forwarding signals. If your application runs as PID 1 without those duties, you get zombies and containers that ignore docker stop.

Before you start

You should understand processes, signals and the container lifecycle. This article covers PID 1 duties and the init shim.

Step-by-step walkthrough

Step 1: PID 1 must reap children

When a child process exits, its parent must call wait to collect its exit status; otherwise it becomes a zombie. As PID 1, an application inherits orphaned processes and must reap them, which most applications do not do.

Step 2: Put the command in exec form

Use the JSON exec form CMD ["node", "server.js"], not the shell form CMD node server.js. The shell form runs the command under /bin/sh, so /bin/sh is PID 1 and the app is a child; the shell may not forward SIGTERM, and the app never sees it, so docker stop times out and escalates to SIGKILL.

Step 3: Use an init shim

Adding --init (or an explicit tini) inserts a tiny init as PID 1 that reaps zombies and forwards signals to your process. It is the simplest fix and costs almost nothing, so most containers should use it.

Worked scenario

The shim reaps children and forwards signals.

FROM node:22-alpine
WORKDIR /app
COPY . .
# exec form keeps the app as the direct target of signals
CMD ["node", "server.js"]
# run with: docker run --init ...
dockerfile

Walk through the example

The exec form ensures the app, not a shell, receives SIGTERM, so it can shut down gracefully. Running with --init puts the init shim at PID 1, which forwards the signal and reaps any orphaned children. Together they make the container stop cleanly and avoid zombies.

Common mistake

Using the shell form of CMD, so signals go to the shell and the app is killed hard. Another is running multi-process containers without an init, so exited children become zombies.

Verify the behavior

Send docker stop with the shell form and observe the timeout and hard kill; switch to exec form and confirm a fast, graceful exit. Run a child process that exits and confirm zombies accumulate without --init and are reaped with it.

Interview exercise

Why does the shell form of CMD break graceful shutdown?

Answer and reasoning

It runs /bin/sh -c "node server.js", so the shell is PID 1 and the app is its child. The shell often does not forward SIGTERM, so the app never receives the stop signal, and the runtime waits out the grace period before sending SIGKILL. The exec form makes the app PID 1 and the direct signal target.

Continue learning

Compare signal handling in Docker signal handling and Entrypoint vs CMD. Read the Docker init documentation and try the Docker interview questions.

More in Docker

read ✓Docker · mid

Docker BuildKit and Cache Mounts

Speed up image builds with BuildKit cache mounts, multi-stage builds and dependency-first layer ordering.

~2 min readread →
read ✓Docker · mid

Docker Compose Profiles

Start only the services you need with Compose profiles, and keep the default set small for focused local development.

~2 min readread →
esc