CMD vs ENTRYPOINT
Two ways to specify what a container runs
Section titled “Two ways to specify what a container runs”Docker gives you two instructions for defining what happens when a container starts:
CMD— the default command (or default arguments). Easily overridden atdocker run.ENTRYPOINT— the fixed executable. Harder to override; makes the container behave like a specific program.
They are often confused. Understanding the difference lets you build containers that are easy to use correctly.
Exec form vs shell form
Section titled “Exec form vs shell form”Both CMD and ENTRYPOINT accept two syntax forms.
Exec form (preferred)
Section titled “Exec form (preferred)”CMD ["node", "server.js"]ENTRYPOINT ["node"]Arguments are a JSON array. Docker executes the program directly — no shell involved. PID 1 inside the container is your process. This means it receives OS signals (SIGTERM, SIGINT) properly, which is critical for graceful shutdown.
Shell form
Section titled “Shell form”CMD node server.jsENTRYPOINT nodeDocker wraps the command in /bin/sh -c "...". The shell becomes PID 1, not your process. Your app runs as a child of the shell, and OS signals are not forwarded reliably. Avoid this form for production services.
CMD — the default command
Section titled “CMD — the default command”CMD sets the default command a container runs. If the user passes a command on docker run, it replaces CMD entirely.
# syntax=docker/dockerfile:1FROM alpine:3.24CMD ["echo", "Hello from CMD!"]# Uses CMDdocker run --rm myimage# Output: Hello from CMD!
# Overrides CMD completelydocker run --rm myimage echo "Custom message"# Output: Custom messageENTRYPOINT — the fixed executable
Section titled “ENTRYPOINT — the fixed executable”ENTRYPOINT sets the executable that always runs. Arguments passed on docker run are appended to the entry point — not substituted.
# syntax=docker/dockerfile:1FROM alpine:3.24ENTRYPOINT ["ping"]CMD ["-c", "3", "8.8.8.8"]# Uses ENTRYPOINT + CMD defaultsdocker run --rm pinger# Runs: ping -c 3 8.8.8.8
# Appends to ENTRYPOINT, replaces CMDdocker run --rm pinger -c 1 1.1.1.1# Runs: ping -c 1 1.1.1.1ENTRYPOINT + CMD together (the pattern)
Section titled “ENTRYPOINT + CMD together (the pattern)”The most flexible and idiomatic pattern:
ENTRYPOINT= the executable (fixed)CMD= default arguments (replaceable by the user)
# syntax=docker/dockerfile:1FROM node:24-alpineWORKDIR /appCOPY server.js .ENTRYPOINT ["node"]CMD ["server.js"]# Default: node server.jsdocker run --rm myapp
# Override: node --versiondocker run --rm myapp --versionOverriding ENTRYPOINT
Section titled “Overriding ENTRYPOINT”You can still override ENTRYPOINT when needed using the --entrypoint flag:
docker run --rm --entrypoint sh myappThis drops you into a shell inside the container — useful for debugging.
Summary table
Section titled “Summary table”| No override | docker run img args | docker run --entrypoint X img | |
|---|---|---|---|
| Only CMD | runs CMD | replaces CMD with args | n/a — uses new entrypoint |
| Only ENTRYPOINT | runs ENTRYPOINT | appends args to ENTRYPOINT | replaces ENTRYPOINT |
| ENTRYPOINT + CMD | runs ENTRYPOINT + CMD defaults | replaces CMD with args | replaces ENTRYPOINT |
Hands-on practice
Section titled “Hands-on practice”# syntax=docker/dockerfile:1
FROM alpine:3.24
# ENTRYPOINT sets the fixed executable
# CMD sets the default arguments
ENTRYPOINT ["echo"]
CMD ["Hello from ENTRYPOINT + CMD!"]
# --- build ---
# docker build -t greet .
# Run with defaults (uses CMD):
# docker run --rm greet
# Override CMD with your own message:
# docker run --rm greet "My custom message"
# Override ENTRYPOINT entirely:
# docker run --rm --entrypoint sh greetTrade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
CMD | Easy to override at docker run, good for dev/debug | A user can accidentally change the whole default behavior without meaning to |
ENTRYPOINT | Fixed, forces the container to always do one job, safer in production | Harder to override — needs --entrypoint, which makes debugging more awkward |
Common mistakes
Section titled “Common mistakes”- Using shell form instead of exec form, so the process is not PID 1, does not receive OS signals, and cannot shut down gracefully.
- Putting both the executable and its arguments into
ENTRYPOINT, leavingCMDwith nothing left to override. - Assuming arguments passed to
docker runalways append toCMD, when they actually replaceCMDentirely.
💡 Real-world example
The official
postgresimage usesENTRYPOINT ["docker-entrypoint.sh"]as its fixed executable andCMD ["postgres"]as the default arguments, so users can override just thepostgresarguments without touching the entrypoint script (github.com/docker-library/postgres).