Docker Entrypoint vs CMD: Mastering Container Process Management
Confused by docker entrypoint vs cmd? Learn how to handle signals, prevent zombie processes, and master container startup best practices for production.
If you've ever sent a docker stop command only to wait ten seconds for a forced kill, you've run into a classic container process management failure. Misunderstanding the interplay between ENTRYPOINT and CMD is often the culprit behind unresponsive containers and ignored termination signals.
Understanding Docker Entrypoint vs CMD
At a high level, the distinction is simple: ENTRYPOINT defines the executable that runs when your container starts, while CMD provides the default arguments passed to that executable. When you combine them, you create a robust command line that handles both static and dynamic inputs.
If you omit ENTRYPOINT, the container simply runs the CMD as the main process (PID 1). This is where things get dangerous. If your CMD is a shell script, the script becomes PID 1, and shell scripts famously don't forward Unix signals (like SIGTERM) to their child processes. That's why your container hangs during deployment—the app never receives the signal to shut down gracefully.
Why Shell Form vs. Exec Form Matters
I learned this the hard way while setting up a CI/CD Pipeline & Docker Containerization project last year. We had a Node.js app that wouldn't stop, and it turned out we were using the shell form in our Dockerfile:
Dockerfile# The wrong way ENTRYPOINT ["node app.js"]
Because of the quotes, Docker runs this as /bin/sh -c "node app.js". The shell consumes the SIGTERM, but it doesn't pass it to node. To fix this, you must use the "exec" form:
Dockerfile# The right way ENTRYPOINT ["node", "app.js"]
In the exec form, node becomes PID 1. It receives the signal directly and can handle cleanup tasks like closing database connections or flushing logs.
Practical Comparison
| Feature | ENTRYPOINT | CMD |
|---|---|---|
| Purpose | Defines the main executable | Provides default arguments |
| Override | Harder to override (needs --entrypoint) | Easily overridden at runtime |
| Signal Handling | Excellent (if exec form used) | Poor (if shell form used) |
| Best Practice | Use for the core application | Use for defaults/flags |
Fixing Signal Handling Issues
When you need to perform setup tasks (like running migrations) before starting your main app, you might be tempted to use a wrapper script. If you do this, you must use exec inside your script to replace the shell process with your application process.
Bash#!/bin/sh # entrypoint.sh echo "Running migrations..." ./migrate.sh # The 'exec' here is critical exec node app.js
Without that exec, your application will be a child of the shell, and you’ll continue to struggle with Linux Process Management: Using lsof and fuser for Zombie Processes when the container fails to terminate. If you find your startup logic getting complicated, check out Debugging Docker Compose Healthcheck: Solving Startup Race Conditions to ensure your orchestration layer is actually waiting for the right state.
Container Startup Best Practices
- Always prefer exec form: Use
["executable", "param1"]syntax to avoid PID 1 issues. - Keep it simple: If you don't need to pass dynamic arguments, just use
ENTRYPOINT. - Use
tiniif necessary: If your application is a complex process manager (like a Java app or a multi-process Python script), consider usingdocker run --initor addingtinito your image. It acts as a lightweight init system that handles zombie process reaping automatically. - Avoid shell scripts if possible: If you can configure your app via environment variables, do that instead of writing a complex
entrypoint.sh.
Troubleshooting Checklist
If your container is acting weird, run docker inspect <container_id> and look at the State and Config sections. If you see the command wrapped in /bin/sh -c, you've found your bug.
Also, verify your image layers. Sometimes, Docker build cache debugging: Fix slow incremental builds reveals that you're accidentally running an older version of your entrypoint script because a previous layer was cached.
FAQ
Q: Can I use both ENTRYPOINT and CMD?
Yes, and it's the recommended pattern. Use ENTRYPOINT for the application binary and CMD for default flags. The user can then append arguments at runtime, and they will be passed to your ENTRYPOINT as CMD values.
Q: What is a zombie process in Docker? A zombie process occurs when a child process finishes but the parent process (PID 1) doesn't "reap" it. If your entrypoint isn't designed to handle signal forwarding or process reaping, these processes accumulate and eventually consume system resources.
Q: Is it ever okay to use the shell form?
Only if you are running a very simple, ephemeral tool where signal handling doesn't matter, or if you explicitly need shell variable expansion (though you can usually handle that in a script using exec).
Next time I'm setting this up, I'll probably just use an init process by default for anything non-trivial. It's too easy to lose an hour debugging a process that simply refuses to die because of a missing exec call.

