CMD vs ENTRYPOINT
This is the most-confused pair in the whole Dockerfile, and getting it wrong produces containers that ignore your arguments, swallow signals, or refuse to take a command at all. The clean mental model: ENTRYPOINT is the fixed executable the container always runs, and CMD is the default arguments handed to it, which docker run can override.
For Driftwood, ENTRYPOINT ["gunicorn"] plus CMD ["--bind", "0.0.0.0:8000", "app:app"] means the container always runs gunicorn with overridable defaults — and the exec-vs-shell choice from topic 22 decides whether gunicorn is PID 1 and answers your docker stop. The two instructions and the form choice together decide what runs, how it is tuned, and whether it shuts down cleanly.
ENTRYPOINT — The Fixed Executable
ENTRYPOINT sets the program the container runs, and it is not replaced by arguments to docker run — those become its arguments instead. It is how you make an image behave like a single command: with ENTRYPOINT ["gunicorn"] set, docker run driftwood/web --workers 4 passes --workers 4 to gunicorn rather than replacing it. The image is gunicorn, and the run command tunes it.
CMD — The Default Arguments
CMD provides defaults that docker run can override. With an ENTRYPOINT set, CMD supplies its default arguments; without one, CMD is the whole default command. Either way, anything you type after the image name on docker run replaces it. That is the lever you reach for when you want a sensible default invocation that a caller can still swap out.
docker run become its arguments — overridden only by --entrypoint.docker run replaces it.The Two Together
ENTRYPOINT fixes what runs, CMD supplies the default arguments, and docker run image <args> swaps the CMD while keeping the ENTRYPOINT. That is the pattern for a tool image with a sensible default invocation: the program is fixed, the arguments have defaults, and a caller overrides only the arguments.
ENTRYPOINT ["gunicorn"] CMD ["--bind", "0.0.0.0:8000", "app:app"] # docker run driftwood/web → gunicorn --bind 0.0.0.0:8000 app:app # docker run driftwood/web --workers 4 app:app → gunicorn --workers 4 app:app
The first run uses the CMD defaults; the second replaces them while gunicorn stays fixed because it lives in the ENTRYPOINT. Read the two lines together and the contract is plain: gunicorn is the program, and everything after the image name is its arguments.
Exec Form and PID 1
Exec form — ENTRYPOINT ["gunicorn", ...] — makes gunicorn PID 1, so it receives SIGTERM from docker stop and shuts down cleanly. Shell form — ENTRYPOINT gunicorn ... — inserts /bin/sh -c, which becomes PID 1, does not forward signals, and leaves docker stop to time out and SIGKILL after 10 seconds. This is the topic-15 signal lesson made concrete: the form you pick here is the difference between a graceful drain of in-flight requests and a hard kill.
Overriding at Run Time
docker run --entrypoint <other> image replaces the ENTRYPOINT — handy for dropping into a shell to debug — while arguments after the image name replace the CMD. Knowing which lever moves which half is the difference between debugging cleanly and fighting the image: docker run driftwood/web bash hands bash to gunicorn as an argument and fails, but docker run --entrypoint bash driftwood/web drops you into a shell.
ENTRYPOINT — declares the fixed executable the container always runs; docker run arguments become its arguments, not a replacement, and --entrypoint is needed to override it. Use it for the program the image exists to run.
CMD — declares the default command-or-arguments and is replaced by anything you type after the image name on docker run. Use it for the overridable defaults. Write both in exec form ([...]) so the process is PID 1 and receives signals.
- Using shell form
ENTRYPOINT gunicorn --bind 0.0.0.0:8000 app:app—/bin/sh -cbecomes PID 1, gunicorn never seesSIGTERM, anddocker stophangs 10 seconds thenSIGKILLs, dropping in-flight requests. - Putting the whole command in
ENTRYPOINTand leavingCMDempty when you wanted run-time-overridable arguments — nowdocker run image --workers 4cannot replace the args, only--entrypointdoes, and the image feels rigid. - Expecting
docker run image bashto drop you into a shell whenENTRYPOINT ["gunicorn"]is set —bashbecomes a gunicorn argument, not the command; you need--entrypoint bash. - Defining
CMDtwice (orENTRYPOINTtwice) in one Dockerfile and being surprised only the last one applies — earlier ones are silently ignored.
- Set
ENTRYPOINTto the fixed executable andCMDto its overridable default arguments when the image exists to run one program, sodocker run image <args>tunes it cleanly. - Write both
ENTRYPOINTandCMDin exec form (JSON array) so the process is PID 1 and receivesSIGTERM, lettingdocker stopshut down gracefully. - Use
--entrypointto override the executable for debugging (drop to a shell) rather than rewriting the Dockerfile. - Reach for an
entrypoint.shscript (whichexec "$@"as PID 1) when startup needs setup steps, so the real process still ends up as PID 1.
Entrypoint and Cmd identically, so Podman and containerd honor the same semantics
Kubernetes renames them command (= ENTRYPOINT) and args (= CMD) — a frequent confusion at the orchestration boundary
Buildpacks set a default process type instead of a hand-written ENTRYPOINT
Knowledge Check
Which of ENTRYPOINT and CMD is overridden by docker run arguments?
CMDis replaced by arguments after the image name;ENTRYPOINTstays fixed unless--entrypointis usedENTRYPOINTis replaced by arguments after the image name;CMDstays fixed unless rebuilt- Both of them are replaced together at once by any positional arguments that are passed to a
docker runinvocation after the image name - Neither can be changed at run time; both are frozen into the image config at build time
How do ENTRYPOINT and CMD combine?
ENTRYPOINTis the fixed executable andCMDsupplies its default, overridable argumentsCMDruns first as a setup step, thenENTRYPOINTruns as the main process- The daemon runs whichever of the two is listed last in the Dockerfile and silently ignores the other
- They are concatenated into a single string and passed together to
/bin/sh -cat startup
Why does shell-form ENTRYPOINT break signal delivery?
- It inserts
/bin/sh -cas PID 1, which does not forwardSIGTERM, sodocker stoptimes out andSIGKILLs - Shell form disables signal handling in the kernel for the entire container namespace
docker stopsends a different, weaker signal to shell-form containers than it does to exec-form ones- Shell form always runs the actual app process in the background, so PID 1 exits almost immediately and the real app is left orphaned
How do you override each at run time when ENTRYPOINT ["gunicorn"] is set?
- Arguments after the image name replace
CMD;--entrypointreplaces the executable docker run image bashdrops you into a shell, replacing theENTRYPOINTdirectly- A
--cmdflag replaces theCMDand a--argsflag replaces theENTRYPOINT - Neither can be overridden once set; you must rebuild the image with a new
ENTRYPOINT
You got correct