Docker · commonly confused
ENTRYPOINT vs CMD
Short answer
First published
On this page · 10 sections
In simple terms
Think of it as a sentence with a fixed verb and a changeable object. ENTRYPOINT is the verb — the thing this image does. CMD supplies the default object, and the person running the container can swap that object out without changing the verb.
That is why an image built to be a database has an ENTRYPOINT of the database binary, and why docker run mydb --help gives you the database's help rather than trying to run a program called --help.
If you only set CMD, there is no fixed verb — the whole sentence is replaceable, and docker run myimage bash gives you a shell instead of your application.
Words used on this page
- ENTRYPOINT
- The Dockerfile instruction that sets the program a container always runs.
docker runarguments are added to it, and onlydocker run --entrypointreplaces it. - CMD
- The Dockerfile instruction that sets defaults: the whole command when there is no ENTRYPOINT, or the default arguments to ENTRYPOINT. Anything after the image name in
docker runreplaces it. - exec form
- An instruction written as a JSON array, such as
["node", "server.js"]. Docker starts the program directly, with no shell, so nothing like$HOMEis expanded. - shell form
- An instruction written as a plain string, such as
node server.js. Docker runs it through/bin/sh -c, which expands variables and understands&&, and can leave the shell between Docker and your program. - PID 1
- The first process in the container.
docker stopsends its signal to this process, and the container stops when it exits. - SIGTERM and SIGKILL
- Signals: messages the kernel delivers to a process. SIGTERM asks it to shut down and can be handled; SIGKILL ends it at once and cannot be caught, blocked or ignored.
- stop timeout
- How long
docker stopwaits after the first signal before sending SIGKILL: 10 seconds by default for Linux containers and 30 for Windows containers.docker stop -tanddocker run --stop-timeoutchange it. - exit code 137
- The status a container shows when SIGKILL (signal 9) ended it, as in
Exited (137).docker ps -a --filter exited=137lists such containers. - --init
- A
docker runoption that starts a small init process as PID 1, which forwards signals to your program and reaps finished child processes.
What actually happens
With an exec-form ENTRYPOINT, Docker concatenates them at start-up: the final command is ENTRYPOINT + CMD. A shell-form ENTRYPOINT ignores CMD and any docker run arguments. Arguments passed to docker run after the image name replace CMD entirely; they do not append to it. Replacing ENTRYPOINT requires the explicit --entrypoint flag.
Both have two forms and the difference matters more than the ENTRYPOINT/CMD distinction does. Exec form — ["nginx", "-g", "daemon off;"] — runs the binary directly as PID 1. Shell form — nginx -g "daemon off;" — wraps it in /bin/sh -c, and the shell can become PID 1 with your process as its child. Docker's own example on ubuntu shows exactly that. Some shells replace themselves with a single command, so check with ps rather than assume either way.
That wrapping is what breaks signal handling. Docker sends SIGTERM to PID 1, and sh -c does not forward signals to children. So your application never receives the shutdown signal, sits there until the stop timeout (10 seconds by default for Linux containers) expires, and gets SIGKILLed — which looks exactly like a hung application and is actually a quoting choice in a Dockerfile.
The pattern worth copying
# Exec form for both. The brackets are load-bearing. ENTRYPOINT ["node", "server.js"] CMD ["--port", "3000"] # docker run myapp -> node server.js --port 3000 # docker run myapp --port 8080 -> node server.js --port 8080 # docker run --entrypoint sh myapp -> sh (ENTRYPOINT overridden explicitly)
Written in shell form as ENTRYPOINT node server.js, the CMD's --port 3000 is ignored, and if sh stays as PID 1, node stops receiving SIGTERM and every stop waits out the timeout, 10 seconds by default on Linux, before Docker kills it.
Try it yourself
Before you start
- Docker able to build and run images:
docker versionprints a Server section, anddocker buildworks in the directory that holds your Dockerfile. - What the image currently stores.
docker image inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' <image>prints both as stored. A list that begins with the shell,["/bin/sh", "-c"]by default on Linux, came from shell form. - A container you can stop and recreate freely, because checking shutdown behaviour means running
docker stopon it and timing it. psinside the image if you want to see which process is PID 1 withdocker exec. Minimal images may not include it;docker top <container>lists the processes from the host instead.
A shell-form ENTRYPOINT that ignores docker stop
Quoted from the shell form ENTRYPOINT example in Docker's Dockerfile reference, not reproduced: Docker is not installed on the machine this page was checked on, and the reference does not say which Docker version or ubuntu image it used. The image, top, was built from FROM ubuntu, ENTRYPOINT top -b and CMD -- --ignored-param1, then started with docker run -it --name test top --ignored-param2. The top screen that run printed is left out here.
Command
docker exec -it test ps waux /usr/bin/time docker stop test
Output · as shown in Docker docs: Dockerfile reference, shell form ENTRYPOINT example
$ docker exec -it test ps waux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.4 0.0 2612 604 pts/0 Ss+ 13:58 0:00 /bin/sh -c top -b --ignored-param2 root 6 0.0 0.1 5956 3188 pts/0 S+ 13:58 0:00 top -b root 7 0.0 0.1 5884 2816 pts/1 Rs+ 13:58 0:00 ps waux $ /usr/bin/time docker stop test test real 0m 10.19s user 0m 0.04s sys 0m 0.03s
How to read it
- PID 1 is
/bin/sh -c top -b --ignored-param2: the shell that Docker wrapped the shell-form ENTRYPOINT in.topis PID 6, a child of that shell. --ignored-param1from CMD appears nowhere.--ignored-param2fromdocker runreached the shell only:top's own command line is justtop -b. A shell-form ENTRYPOINT ignores both.docker stoptook 10.19 seconds. Its SIGTERM went to PID 1, the shell, which did not pass it on, so Docker waited out the stop timeout and then sent SIGKILL.- The same reference shows two fixes. With
ENTRYPOINT exec top -b,topis PID 1 anddocker stopreturns in 0.20 seconds. With the exec form,ENTRYPOINT ["top", "-b"], there is no shell at all andtopis PID 1.
The mistake this causes
Using shell form, then wondering why the container ignores shutdown signals.
Why people do it Shell form looks tidier and matches how you would type the command in a terminal. The consequence — that /bin/sh -c becomes PID 1 and swallows signals — is invisible until something needs to stop cleanly.
What you see Containers take about 10 seconds to stop, every time: the default stop timeout for Linux containers, or whatever --stop-timeout sets. Graceful shutdown code never runs, so in-flight requests are dropped on every deploy and connections are not closed. docker inspect shows exit code 137, which is SIGKILL — the kernel eventually forcing what SIGTERM should have achieved politely.
If it goes wrong
docker stoptakes about 10 seconds, anddocker ps -ashowsExited (137)- PID 1 did not act on SIGTERM, so Docker killed it. Run
docker top <container>: if a/bin/sh -c ...process sits above your program, rewrite the instruction in exec form or start the shell-form command withexec. If PID 1 is your program, it needs its own SIGTERM handler: the kernel delivers SIGTERM sent from outside to a container's PID 1 only when a handler is installed.docker run --initadds an init that forwards the signal. - Arguments after the image name in
docker runare silently ignored - The ENTRYPOINT is in shell form, which ignores CMD and run arguments. Rewrite it in exec form, and write CMD in exec form too: a shell-form CMD reaches an exec-form ENTRYPOINT as
/bin/sh -c ...arguments. docker run <image> --helpexits with code 127, saying the command--helpwas not found- The image has no ENTRYPOINT, so your arguments replaced CMD and became the command. Docker uses exit code 127 when the command cannot be found. Name the program as well,
docker run <image> <program> --help, or give the image an exec-form ENTRYPOINT. - A variable such as
$HOMEreaches the program as the literal text$HOME - Exec form runs no shell, so nothing expands variables. Either call a shell explicitly,
["sh", "-c", "exec myapp --home $HOME"], or move the logic into an entrypoint script that ends withexec "$@". - After adding ENTRYPOINT, the default command from the base image is gone
- Setting ENTRYPOINT resets a CMD inherited from the base image to empty, and
docker run --entrypointclears the image's CMD too. Set CMD again in your own Dockerfile, or pass the arguments on the command line.
How it shows up in production
This one hides for a long time because nothing fails outright. Deploys are just slower than they should be, and occasionally a user sees an error during a rollout.
It surfaces properly in Kubernetes, where the same problem means a pod ignores its termination signal for the whole terminationGracePeriodSeconds, then gets killed. Kubernetes stops sending it regular Service traffic, but the requests and connections it already has are cut off by the kill, and the resulting errors look like a load-balancer problem rather than a Dockerfile one.
How to tell which one you are hitting
- Is your process actually PID 1?
- Run
docker exec <container> ps -o pid,command look at what PID 1 is. If it isshor/bin/sh, your application is a child and will not receive signals. - Does the container take about 10 seconds to stop?
- That is the default grace period expiring. A container whose process receives SIGTERM stops as soon as that process has shut down: 0.20 seconds in Docker's
topexample.
Security considerations
- When SIGTERM never reaches the program, every stop ends in SIGKILL, which cannot be caught. The program gets no chance to finish writes, flush buffers or close connections, so data in flight can be lost or left half-written on every deploy.
- ENTRYPOINT is not a lock. Anyone who can run the image can replace it with
--entrypoint, for example with a shell, so never rely on it to keep people away from files, tools or secrets inside the image. - An entrypoint script that must start as root, for example to
chowna data directory, should drop privileges as it hands over: Docker's example ends withexec gosu postgres "$@", so the server runs aspostgresand as PID 1. Withoutexec, a root shell stays in the container as the server's parent. - An entrypoint script ending in
exec "$@"runs whatever command the container was started with. That is what makesdocker run <image> bashwork, so anyone who can start the container can run any program in it as the script's user. - If your program starts child processes, run it with
--initor an init inside the image. As PID 1, your program becomes the parent of every orphaned process in the container and must reap them, and SIGTERM reaches it only if it installs a handler.
In an interview
How this gets asked in an interviewpreparation
Usually phrased as “What is the difference between ENTRYPOINT and CMD?”
What a strong answer contains Say that they concatenate, that docker run arguments replace CMD but not ENTRYPOINT, and then bring up exec versus shell form unprompted. The second point is the one that shows production experience, because it is the one that causes real incidents.
The follow-up “Why does exec form matter?” leads into PID 1, signal handling and graceful shutdown, which connects straight to how rolling deployments avoid dropping requests.
Sources
The behaviour this page describes, as documented by the projects themselves.
- Docker docs: Dockerfile reference, shell and exec form
- Docker docs: Running containers, default entrypoint and exit status
- Docker docs: docker container ls (exited=137 filter)
- Docker Engine API v1.56 (ImageConfig Entrypoint and Cmd)
- Kubernetes: Pod lifecycle, termination of Pods
- pid_namespaces(7): signals to a namespace's init process
- signal(7) Linux manual page
- Docker — Dockerfile reference
- Docker — docker container stop
- Docker — docker container run
Did this get you to an answer?
No text box on purpose — please do not paste production logs anywhere
Something wrong or out of date? Report it