The Issue: Why Do Your Docker Containers Always Take 10 Seconds to Shut Down?
If you run docker stop and have to wait exactly 10 seconds for the container to stop, it’s not because your application is heavy. In fact, you’re facing a Signal Handling issue. Docker sends a SIGTERM signal requesting the app to close connections and shut down gracefully. If the app “ignores” this for more than 10 seconds, Docker loses patience and uses SIGKILL to force-kill the process immediately.
Many people mistakenly assume the delay is caused by slow database disconnections. However, the root cause often lies in how Linux manages PID 1 inside the Container. Most popular runtimes like Node.js, Python, or Java aren’t designed to act as a professional Init Process.
In a past project, I struggled with a server that reported PID exhaustion every week, even though RAM was still free. After checking with the ps aux command, I was shocked to find hundreds of processes in a <defunct> state. These were Zombie Processes accumulating because they weren’t cleaned up properly.
Why is PID 1 So Important?
Responsibilities of the “Ancestor Process”
In Linux, the process with ID 1 (PID 1) is the root of all other processes. It carries two essential responsibilities:
- Signal Forwarding: When you press Ctrl+C or run the stop command, the system sends a
SIGINT/SIGTERMsignal. PID 1 must receive and “forward” this to all child processes. - Reaping: When a child process ends, it doesn’t disappear immediately; it becomes a Zombie. PID 1 is responsible for acknowledging it so the Linux kernel can release its resources.
When you declare ENTRYPOINT ["node", "index.js"], Node.js occupies PID 1. Unfortunately, Node.js doesn’t automatically clean up its children or forward signals like systemd or init do on a standard OS.
Consequences of Zombie Processes
Imagine your application calls a Shell script to process images or send emails. If that script finishes and PID 1 doesn’t “reap” it, the process stays in the Linux kernel’s process table forever. For systems running thousands of short-lived tasks per hour, these zombies can hang the entire server by exhausting the PID table.
Tini – A “Small but Mighty” Solution
Tini is a lightweight Init Process, only about 20-30 KB and written in C. It was created to do one thing: be a professional PID 1 for containers. Tini will handle signals on behalf of your app and clean up any zombies that arise.
# How Tini works:
[tini] (PID 1) --> [app] (PID 2) --> [child] (ZOMBIE!)
# Tini detects child termination --> performs Reaping --> Releases PID table entry.
3 Ways to Integrate Tini into Your Workflow
Method 1: Using the –init Flag (Fastest and Easiest)
Since version 1.13, Docker has built-in Tini support. You don’t need to modify your code or Dockerfile; just add the --init flag when starting the container.
docker run --init -d my-app:latest
This is the best way to quickly check if your application has Signal Handling issues without rebuilding the image.
Method 2: Bundling Directly into the Dockerfile (Production Standard)
To ensure containers run stably across all environments like Kubernetes or AWS ECS, you should install Tini directly into the Image. Here is an example with Alpine Linux:
FROM python:3.9-alpine
RUN apk add --no-cache tini
WORKDIR /app
COPY . .
# Always use Tini as ENTRYPOINT
ENTRYPOINT ["/sbin/tini", "--"]
# Main application defined in CMD
CMD ["python", "app.py"]
Pro Tip: Always use the exec form (the array format ["..."]). If you use the shell form (python app.py), Docker wraps the command in /bin/sh -c. In this case, the Shell becomes PID 1, and the original issue persists.
Method 3: Configuration via Docker Compose
For projects using Compose, simply add a single attribute line in your docker-compose.yml file:
services:
api:
image: my-node-app
init: true
ports:
- "8080:8080"
Warning When Using Bash Scripts as Entrypoints
Many of you write entrypoint.sh files to run migrations before starting the app. If you write /usr/bin/python main.py on the last line, the shell script occupies PID 1. When a SIGTERM is received, the script dies immediately, but the internal Python process remains hanging as an “orphan.” Use the exec command to replace the shell process with the app process: exec python main.py.
Conclusion
Using Tini isn’t just about avoiding a 10-second wait during deployment. It’s a standard for building resilient container systems and preventing OS resource leaks. If you’re managing critical services in Production, take 5 minutes to check your PID 1. This small change will make your system much more professional and stable.

