Site icon New Generation Enterprise Linux

That ‘exit 0’ from Your Container? It’s Lying To You.

Container & Kubernetes Internals

That ‘exit 0’ from Your Container? It’s Lying To You.

Technical Briefing | 11/3/2026

You’ve seen it: a container exits, you check `docker wait` or your Kubernetes Pod status, and it’s a nice, clean `0`. All good, right? Not always. I’ve been bitten by this more times than I care to admit in production, where an ‘exit 0’ actually meant the container runtime had to brutally SIGKILL something still running in the background, or worse, your orchestrator had to step in after a timeout. That green light gives a false sense of security.

Inside the Container: Your Application is PID 1

Here’s the thing about processes on Linux: PID 1 is special. It’s the `init` process, the grand-daddy of all processes, and it’s responsible for cleaning up ‘zombie’ child processes. If PID 1 doesn’t reap its dead children, they just hang around as entries in the process table, consuming resources. A proper `init` system, like `systemd` or `sysvinit`, also handles signals gracefully. When a container starts, whatever you specify in `ENTRYPOINT` or `CMD` often becomes PID 1 *inside* that container’s PID namespace. And most applications? They’re just not written to be `init`. They don’t reap zombies. They might not even handle `SIGTERM` (the signal your orchestrator sends for a graceful shutdown) correctly.

FROM alpine:latest
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
---
#!/bin/sh


echo "Starting background sleep..."
sleep 60 &
BG_PID=$!
echo "Background sleep started with PID $BG_PID. My PID is $$."
echo "Main process (PID $$) exiting immediately with 0."
exit 0
---
docker build -t lying-container .
CONTAINER_ID=$(docker run -d lying-container)
echo "Container ID: $CONTAINER_ID"
sleep 2 # Give it a moment to print logs
docker logs $CONTAINER_ID
# You'll see the entrypoint exiting, but the container still runs for a bit
docker ps -f id=$CONTAINER_ID
EXIT_CODE=$(docker wait $CONTAINER_ID)
echo "The observed exit code: $EXIT_CODE"
docker ps -f id=$CONTAINER_ID # Should be Exited now

Making Your Container an Honest Citizen

  • Use an `init` wrapper: Tools like `tini` or `dumb-init` are designed exactly for this. They become PID 1, handle signals, and reap zombies, passing control to your actual application. Many Docker images (especially official ones) include `tini` by default. Just run `docker run –init`.
  • Use the `exec` form for `ENTRYPOINT` and `CMD`: When you write `ENTRYPOINT [“/app”, “–arg”]` (JSON array form), `/app` becomes PID 1. If you use `ENTRYPOINT /app –arg` (shell form), `/bin/sh -c “/app –arg”` runs, making `/bin/sh` PID 1. Often, `sh` is better at signal handling than your app, but you still rely on its default behavior.
  • Write your application to be PID 1 aware: If you absolutely can’t use an init wrapper, your application must handle `SIGTERM` (and potentially `SIGINT`) for graceful shutdown and implement zombie reaping. This is a heavy lift for most app developers, honestly, and usually unnecessary if you use an init wrapper.

Orchestrators like Kubernetes and Docker Compose aren’t magic. They work with the signals. When a Pod or service needs to shut down, the orchestrator sends a `SIGTERM` to the container’s PID 1, waits for a configurable period (Kubernetes’ `terminationGracePeriodSeconds`), and then, if the container is still running, sends a brutal `SIGKILL`. If your app, as PID 1, ignores that `SIGTERM` or doesn’t gracefully shut down its children, you’re just waiting for the axe to fall.

And that’s why an exit code of `137` (which means `SIGKILL`, by the way, `128 + 9`) is usually a symptom of a deeper problem, not the problem itself. It means something wasn’t ready to die cleanly, or it didn’t listen to the polite `SIGTERM` that came before. Chasing down a `137` often leads back to understanding what processes are actually running, and which one is PID 1, when your container is shutting down.

So, the next time you see a clean exit code, take a second. Does your `ENTRYPOINT` actually manage its children and handle signals? If you’re not explicitly using an `init` wrapper or know for a fact your app is `init`-aware, you’re playing a risky game. It’s an easy thing to overlook, but it’s where many seemingly ‘flaky’ container shutdowns actually hide.

Linux Admin Automation  |  © www.ngelinux.com  |  11/3/2026
0 0 votes
Article Rating
Exit mobile version