Skip to main content
DevOps Insightsguides & answers

Docker · commonly confused

ENTRYPOINT vs CMD

Short answer

ENTRYPOINT is the command that always runs. CMD is the default arguments, and anything you type after the image name replaces them.

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 run arguments are added to it, and only docker run --entrypoint replaces 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 run replaces 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 $HOME is 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 stop sends 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 stop waits after the first signal before sending SIGKILL: 10 seconds by default for Linux containers and 30 for Windows containers. docker stop -t and docker run --stop-timeout change it.
exit code 137
The status a container shows when SIGKILL (signal 9) ended it, as in Exited (137). docker ps -a --filter exited=137 lists such containers.
--init
A docker run option 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 version prints a Server section, and docker build works 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 stop on it and timing it.
  • ps inside the image if you want to see which process is PID 1 with docker 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. top is PID 6, a child of that shell.
  • --ignored-param1 from CMD appears nowhere. --ignored-param2 from docker run reached the shell only: top's own command line is just top -b. A shell-form ENTRYPOINT ignores both.
  • docker stop took 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, top is PID 1 and docker stop returns in 0.20 seconds. With the exec form, ENTRYPOINT ["top", "-b"], there is no shell at all and top is 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 stop takes about 10 seconds, and docker ps -a shows Exited (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 with exec. 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 --init adds an init that forwards the signal.
Arguments after the image name in docker run are 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> --help exits with code 127, saying the command --help was 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 $HOME reaches 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 with exec "$@".
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 --entrypoint clears 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,comm and look at what PID 1 is. If it is sh or /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 top example.

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 chown a data directory, should drop privileges as it hands over: Docker's example ends with exec gosu postgres "$@", so the server runs as postgres and as PID 1. Without exec, 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 makes docker run <image> bash work, 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 --init or 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 interview

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.

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