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.20CMD ["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.20ENTRYPOINT ["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:22-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.20
# 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 greet