Dockerfile — CMD vs ENTRYPOINT
You’ve used CMD in Dockerfiles to tell a container what to run. There’s another instruction that looks like it does almost the same thing — ENTRYPOINT — but the difference becomes important the moment you pass something after the image name.
This is a short one: what ENTRYPOINT does differently, and why CMD isn’t always the right choice.
What They Share
Both can define the command that runs when a container starts.
For the basic case, they can look almost identical:
CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["nginx", "-g", "daemon off;"]Both start nginx when the container runs.
If this were the whole story, there wouldn’t be much reason for two instructions.
What ENTRYPOINT Does Extra
ENTRYPOINT makes the container behave like a specific executable.
The important difference appears when you provide arguments at runtime:
ENTRYPOINT ["python", "app.py"]Now:
docker run myapp --port 8080effectively runs:
python app.py --port 8080The arguments are added to the ENTRYPOINT.
With CMD instead:
CMD ["python", "app.py"]running:
docker run myapp --port 8080replaces the CMD entirely. Docker tries to run:
--port 8080which obviously isn’t what you wanted.
That’s the key distinction:
CMDprovides a default command or default arguments.ENTRYPOINTdefines the executable that the container is built to run.
Why CMD Still Wins Sometimes
ENTRYPOINT sounds more intentional, but that doesn’t make it the better choice for every Dockerfile.
If you want the command to be easily replaced:
CMD ["nginx", "-g", "daemon off;"]is simple and flexible.
You can run:
docker run myapp shand get a shell instead.
With:
ENTRYPOINT ["nginx", "-g", "daemon off;"]the same:
docker run myapp shdoesn’t replace nginx. Instead, Docker effectively tries to run:
nginx -g "daemon off;" shSo ENTRYPOINT is useful when the image represents a specific executable, while CMD is useful when you want the runtime command to remain easy to override.
Note
Docker’s usual pattern is to combine them when it makes sense: use ENTRYPOINT for the fixed executable, and CMD for its default arguments. For example: ENTRYPOINT ["python", "app.py"] with CMD ["--port", "8080"]. Then docker run myapp --port 9000 keeps the application but changes its default argument.
How They Actually Combine
The rule most people remember is “use both”, but it’s worth seeing exactly how Docker merges them, because this is where most confusion starts.
flowchart TD
A["docker run myapp arg1 arg2"] --> B{Are runtime<br/>args given?}
B -->|No| C["CMD supplies the default args"]
B -->|Yes| D["Runtime args replace CMD entirely"]
C --> E["ENTRYPOINT + CMD (or its replacement)<br/>are joined into one command"]
D --> E
E --> F["Final process the container runs"]
Only CMD gets replaced by runtime arguments — ENTRYPOINT never does, unless you explicitly override it (more on that below). So with:
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]docker run myapp→python app.py --port 8080(defaultCMDused)docker run myapp --port 9000→python app.py --port 9000(CMDreplaced)docker run myappcan never accidentally turn into runningbashor anything else —ENTRYPOINTis fixed.
Overriding ENTRYPOINT When You Need To
Sometimes you do need to bypass a fixed ENTRYPOINT — for debugging, or to drop into a shell inside an image that wasn’t built with that in mind. Docker lets you do this with the --entrypoint flag:
docker run --entrypoint sh myappThis replaces ENTRYPOINT for that one run, and any trailing arguments become arguments to the new entrypoint instead of the old one. It’s the escape hatch for images that otherwise never let you get a shell.
docker run --entrypoint sh myapp -c "ls /app"Note
--entrypoint only accepts a single executable — you can’t pass a full JSON array like in the Dockerfile. If you need ENTRYPOINT ["python", "-m", "http.server"] temporarily replaced, you’d override it with --entrypoint python and then supply -m http.server as trailing arguments.
Exec Form vs Shell Form
Both instructions can be written two ways, and the form you pick changes container behavior beyond just CMD-vs-ENTRYPOINT semantics.
# Exec form — recommended
ENTRYPOINT ["python", "app.py"]
# Shell form
ENTRYPOINT python app.pyThe exec form (JSON array) runs the process directly as PID 1. The shell form wraps it inside /bin/sh -c "...", which becomes PID 1 instead, with your actual process running as a child of that shell.
This matters for two reasons:
- Signal handling. When Docker sends
SIGTERMto stop a container, it goes to PID 1. In shell form, the shell receives it — and by default doesn’t forward it to your application. That meansdocker stopcan end up waiting the full timeout and then force-killing withSIGKILL, instead of letting your app shut down gracefully. - Variable substitution. Shell form does support environment variable expansion (
ENTRYPOINT echo $HOME), which exec form does not do automatically. That’s the one case shell form is actually useful for.
Exec form ["cmd", "arg"] | Shell form cmd arg | |
|---|---|---|
| Runs as PID 1 | Yes | No — the shell is PID 1 |
| Receives signals directly | Yes | No, unless the shell forwards them |
| Environment variable expansion | No | Yes |
| Recommended default | Yes | Only when you need shell features |
Warning
If you must use shell form and still want clean shutdowns, exec into the process explicitly: CMD exec python app.py. The exec built-in replaces the shell process with your application instead of running it as a child, so it becomes PID 1 after all.
Wrapping Up
CMD gives you a default that can be replaced. ENTRYPOINT gives the container a fixed executable that runtime arguments are appended to, and one that can still be swapped out explicitly with --entrypoint when you need to.
That’s why neither instruction is simply “better.” Use CMD when you want flexibility over what runs, use ENTRYPOINT when the image is meant to behave like a particular executable, and combine them when you want a fixed program with configurable default arguments. Whichever you use, prefer exec form — it’s the difference between a container that shuts down cleanly and one that always needs a hard kill.