What is the difference between CMD and ENTRYPOINT in a Dockerfile?
ENTRYPOINT sets the program the container always runs, and CMD sets default arguments or a default command that is easy to override. Arguments passed to docker run replace CMD but are appended to ENTRYPOINT; changing ENTRYPOINT needs the --entrypoint flag.
Explain it simply
Think of a chai stall. The ENTRYPOINT is the fact that the stall makes chai; that does not change. The CMD is the default order, "one cutting chai". A customer can say "two masala chai, less sugar" and that replaces the default order, but they are still getting chai. To make the stall sell something else entirely, you have to change the stall itself, which is what --entrypoint does.
Example
FROM alpine:3.20
ENTRYPOINT ["ping", "-c", "3"]
CMD ["localhost"]docker build -t pinger .
docker run pinger
# runs: ping -c 3 localhost
docker run pinger aitapri.com
# runs: ping -c 3 aitapri.com (CMD replaced, ENTRYPOINT kept)
docker run --entrypoint echo pinger hello
# runs: echo hello (ENTRYPOINT replaced, and the old CMD is dropped)If the Dockerfile had only CMD ["ping", "-c", "3", "localhost"], then docker run pinger aitapri.com would try to run a program called aitapri.com and fail, because the whole CMD is replaced.
How it works
Docker builds the final command by joining ENTRYPOINT and CMD:
| Dockerfile has | docker run image | docker run image x y |
|---|---|---|
Only CMD ["a","b"] | a b | x y |
Only ENTRYPOINT ["e"] | e | e x y |
ENTRYPOINT ["e"] and CMD ["a"] | e a | e x y |
Both instructions have two forms.
- Exec form, a JSON array:
CMD ["node", "server.js"]. The program runs directly as process ID 1. - Shell form, plain text:
CMD node server.js. Docker wraps it as/bin/sh -c "node server.js", so the shell is PID 1 and your app is its child.
Exec form is the safer default. When Docker stops a container it sends SIGTERM to PID 1. In shell form the shell receives it and typically does not forward it to your app, so the app cannot shut down cleanly and gets killed with SIGKILL after the timeout (10 seconds by default). Also, if you use shell form for ENTRYPOINT, CMD and run arguments are ignored.
Only the last CMD and the last ENTRYPOINT in a Dockerfile take effect.
Real-world use
- Images for a single tool, like a CLI or a linter, use ENTRYPOINT for the tool and CMD for default flags such as
--help. - Application images often use only CMD, so developers can easily run
docker run myapp shto look inside. - Many official images, such as
postgres, use an entrypoint script that does setup (creating a database, fixing permissions) and then ends withexec "$@"to hand over to the CMD.
Common mistakes
- Using shell form and then wondering why the container takes 10 seconds to stop.
- Expecting
docker run image argsto replace ENTRYPOINT. - Writing an entrypoint script that runs the app without
exec, so the script stays as PID 1 and swallows signals. - Writing several CMD lines and expecting them all to run.
What to say in the interview
"ENTRYPOINT defines the executable the container always runs; CMD supplies default arguments, or a default command if there is no ENTRYPOINT. Anything I pass after the image name in docker run replaces CMD and gets appended to ENTRYPOINT. To replace ENTRYPOINT I need --entrypoint. A common pattern is ENTRYPOINT for the binary and CMD for default flags. I prefer the exec, JSON-array form so my process is PID 1 and receives SIGTERM for a graceful shutdown."
Follow-up questions
- Why does exec form matter for graceful shutdown?
- What does
exec "$@"do at the end of an entrypoint script? - How do Kubernetes
commandandargsmap to ENTRYPOINT and CMD? - What is the PID 1 zombie-reaping problem and how does
--inithelp?
Go deeper
Read these once the basics above feel easy.
In a Kubernetes Pod spec, command overrides the image's ENTRYPOINT and args overrides its CMD. If you set only command, the image's CMD is ignored too. Because PID 1 in Linux does not get default signal handlers and must reap orphaned child processes, many images run a tiny init such as tini, which Docker adds for you with docker run --init.