Dockerfile CMD vs ENTRYPOINT

By Vishvesh Patel · DevOps EngineerUpdated September 16, 2026

CMD vs ENTRYPOINT. Exec vs shell form, how runtime arguments override each, using them together, and why signals get ignored.

Both define what runs when a container starts, and the difference between them is one of the most frequently asked Docker questions.

The short version:

ENTRYPOINT defines what runs. CMD defines the default arguments.

Everything else follows from how each behaves when you pass arguments to docker run.

The behavioural difference

With docker run myimage extra args
CMDReplaced entirely by extra args
ENTRYPOINTextra args are appended to it

That is the whole distinction. A worked example:

FROM alpine
CMD ["echo", "hello"]
docker run myimage                 # → hello
docker run myimage echo goodbye    # → goodbye     (CMD replaced)
FROM alpine
ENTRYPOINT ["echo", "hello"]
docker run myimage                 # → hello
docker run myimage goodbye         # → hello goodbye   (appended)

Using them together

This is the idiomatic pattern, and it is what most well-built images do:

FROM python:3.12-slim
ENTRYPOINT ["python"]
CMD ["app.py"]
docker run myimage                    # → python app.py
docker run myimage other.py           # → python other.py

ENTRYPOINT fixes the executable; CMD supplies a default argument the user can override. The container always runs Python, but which script is up to the caller.

Another common shape. A tool with a sensible default subcommand:

ENTRYPOINT ["curl"]
CMD ["--help"]

Exec form vs shell form

Both instructions accept two syntaxes, and choosing wrongly causes a real and confusing bug.

CMD ["node", "server.js"]      # exec form. JSON array
CMD node server.js             # shell form
Exec formShell form
Runs asThe binary directly, PID 1/bin/sh -c "your command"
Receives signalsYesNo (sh gets them, not your process)
Shell features ($VAR, &&, pipes)NoYes
Requires a shell in the imageNoYes

Use exec form. The signal behaviour is the reason, and it is not a small detail.

Why shell form breaks graceful shutdown

With shell form, /bin/sh is PID 1 and your application is a child process. docker stop sends SIGTERM to PID 1, the shell, which does not forward it. Your application never learns it should shut down.

Docker waits 10 seconds, then SIGKILLs everything.

The symptoms are recognisable: containers always take exactly 10 seconds to stop, in-flight requests are cut off, and graceful shutdown code never runs. On Kubernetes the same problem makes every rolling deploy drop connections.

# Broken, sh is PID 1, your app never sees SIGTERM
CMD node server.js

# Correct, node is PID 1 and receives signals
CMD ["node", "server.js"]

When you genuinely need the shell

Variable expansion and shell operators only work in shell form:

# Exec form does NOT expand $PORT. It passes the literal string
CMD ["node", "server.js", "--port", "$PORT"]     # wrong

# Shell form expands it, but loses signal handling
CMD node server.js --port $PORT                  # works, but PID 1 is sh

The fix that gives you both is exec, which replaces the shell rather than spawning a child:

CMD ["sh", "-c", "exec node server.js --port $PORT"]

exec means the shell process is replaced by node, so node becomes PID 1 and receives signals normally.

Entrypoint scripts

The standard pattern for images needing setup before the main process:

#!/bin/sh
set -e

# Setup: wait for dependencies, run migrations, render config
until nc -z "$DB_HOST" 5432; do
  echo "waiting for database"
  sleep 1
done

# Hand over. Exec replaces this script, so the app becomes PID 1
exec "$@"
COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["node", "server.js"]

Two details make this work. exec "$@" runs whatever CMD supplied, replacing the script so the application ends up as PID 1 with working signal handling. And because CMD is still overridable, docker run myimage sh drops you into a shell while still running the setup first.

Omitting exec, writing just "$@", is the single most common bug in entrypoint scripts. The script stays PID 1, signals go to it, and graceful shutdown breaks.

Overriding at runtime

docker run myimage other-arg              # overrides CMD
docker run --entrypoint /bin/sh myimage   # overrides ENTRYPOINT

--entrypoint is how you get a shell in an image whose entrypoint immediately runs something else. Invaluable for debugging a container that exits instantly.

Note the asymmetry: --entrypoint takes only the executable. Arguments to it go after the image name.

Inheritance

A child image's ENTRYPOINT resets the inherited CMD to null. If you set ENTRYPOINT in a derived image and rely on the base image's CMD, you get nothing:

FROM someimage        # has ENTRYPOINT ["app"] and CMD ["--serve"]
ENTRYPOINT ["wrapper"]
# CMD is now empty. You must restate it
CMD ["--serve"]

Choosing

SituationUse
Image is one specific toolENTRYPOINT + CMD for defaults
Image is a general environmentCMD only, so it is easily overridden
Setup needed before the appENTRYPOINT script ending in exec "$@"
Anything at allExec form, always

Quick reference

TaskSyntax
Fixed executableENTRYPOINT ["node"]
Default argumentsCMD ["server.js"]
Shell features safelyCMD ["sh", "-c", "exec app --port $PORT"]
Override the commanddocker run <image> <cmd>
Override the entrypointdocker run --entrypoint <bin> <image>
Inspect bothdocker inspect --format '{{.Config.Entrypoint}} {{.Config.Cmd}}' <image>

Key takeaways

  • ENTRYPOINT is what runs; CMD is the default arguments.
  • Runtime args replace CMD but are appended to ENTRYPOINT.
  • Always use exec form: the JSON array syntax.
  • Shell form makes /bin/sh PID 1, so your app never receives SIGTERM and every stop takes the full 10-second timeout.
  • CMD ["sh", "-c", "exec app $VAR"] gives variable expansion and correct signal handling.
  • Entrypoint scripts must end in exec "$@", or the script stays PID 1.
  • --entrypoint overrides the entrypoint, which is how you debug a container that exits immediately.
  • Setting ENTRYPOINT in a child image nulls the inherited CMD.

Check your understanding

Exercise

Containers that always take 10 seconds to stop

A Dockerfile uses CMD node server.js in shell form. Every docker stop takes exactly 10 seconds and the application's shutdown handler never runs. Why?

Exercise

Predicting the command

An image has ENTRYPOINT ["python"] and CMD ["app.py"]. What runs for docker run myimage other.py?


Frequently Asked Questions

What is the difference between CMD and ENTRYPOINT in Docker?

ENTRYPOINT defines the executable that always runs; CMD defines the default arguments passed to it. The practical difference shows at runtime: arguments given to docker run replace CMD entirely but are appended to ENTRYPOINT. So an image with ENTRYPOINT ["python"] and CMD ["app.py"] runs python app.py by default and python other.py when you pass other.py. The executable is fixed and the caller controls the arguments.

Should I use CMD or ENTRYPOINT?

Use both together for an image that is one specific tool: ENTRYPOINT fixes the binary, CMD provides overridable defaults. Use CMD alone for a general-purpose image where users may want to run arbitrary commands, such as a language runtime image. Use an ENTRYPOINT script ending in exec "$@" when setup must happen before the main process. Waiting for a dependency, rendering configuration, running migrations.

What is the difference between exec form and shell form?

Exec form is the JSON array syntax, CMD ["node", "server.js"], and runs the binary directly as PID 1. Shell form, CMD node server.js, runs it via /bin/sh -c, making the shell PID 1 with your application as a child. Exec form is strongly preferred because only PID 1 receives signals, with shell form your application never sees SIGTERM and graceful shutdown silently never happens. Shell form does allow variable expansion and pipes, which exec form does not.

Why does my container take 10 seconds to stop?

Because your application is not receiving SIGTERM, so docker stop waits the full grace period and then SIGKILLs it. The usual cause is shell form: /bin/sh is PID 1 and does not forward signals to its child. Switch to exec form so your process is PID 1. If you have an entrypoint script, make sure it ends with exec "$@" rather than plain "$@", since without exec the script remains PID 1 and absorbs the signal.

How do I use an environment variable in CMD?

Exec form does not expand variables, CMD ["node", "app.js", "--port", "$PORT"] passes the literal string $PORT. The safe way to get expansion without losing signal handling is CMD ["sh", "-c", "exec node app.js --port $PORT"]. The exec is essential: it replaces the shell process with your application so it becomes PID 1. Plain shell form expands the variable but leaves the shell as PID 1 and breaks shutdown.

How do I override ENTRYPOINT to debug a container?

docker run --entrypoint /bin/sh -it myimage replaces the entrypoint and drops you into a shell, which is the standard way to inspect an image whose entrypoint exits immediately or crashes. Note that --entrypoint accepts only the executable. Any arguments go after the image name. For a distroless image with no shell at all, copy the filesystem out with docker create and docker cp instead.

Continue Learning

Explore Related Topics

Try the Tool

Practise this in the browser

Docker Playground

Run the commands in a simulated terminal, no install needed.

When it breaks

Related Resources