Dockerfile ENTRYPOINT reference
Last reviewed on 2026-09-25
Exec form vs shell form, how ENTRYPOINT combines with CMD, passing arguments at run time, and writing entrypoint scripts that handle signals correctly.
Syntax
# Exec form (preferred): JSON array, no shell
ENTRYPOINT ["executable", "arg1", "arg2"]
# Shell form: run via /bin/sh -c
ENTRYPOINT command arg1 arg2
ENTRYPOINT sets the executable that runs when the container starts. With the exec form, CMD and any arguments given to docker run <image> are appended to it. Only the last ENTRYPOINT in a Dockerfile takes effect.
Exec form vs shell form
Exec form: ENTRYPOINT ["executable", "arg"]
- It is a JSON array. Every element must be in double quotes.
ENTRYPOINT ['app']is not valid JSON, so Docker silently falls back to the shell form and runs/bin/sh -c "['app']", which fails with "not found". - No shell is involved. Docker runs the first element directly and passes the rest as arguments. There is no variable expansion, globbing, pipes,
&&, or redirection:ENTRYPOINT ["echo", "$HOME"]prints the literal string$HOME. - Your process is PID 1, so it receives
SIGTERMfromdocker stopdirectly. - Arguments are appended.
CMDanddocker runarguments are added after the array.
Shell form: ENTRYPOINT command arg
- Docker wraps the line as
/bin/sh -c "command arg"(cmd /S /Con Windows), so variables like$PORTare expanded at container start. /bin/shis PID 1 and your program is its child. Many shells do not forwardSIGTERM, sodocker stopwaits for the 10-second timeout and then kills the container withSIGKILL.CMDanddocker runarguments are ignored. This is the most common reason "my arguments don't reach the entrypoint".
If you need variable expansion and want to keep the exec form's behaviour, call the shell yourself and exec the program so it replaces the shell as PID 1:
ENTRYPOINT ["sh", "-c", "exec myapp --port \"$PORT\""]
With sh -c, arguments passed after the image name become $0, $1, … inside the script string rather than being appended to myapp. For anything beyond a one-liner, use an entrypoint script.
How ENTRYPOINT and CMD combine
ENTRYPOINT is the executable; CMD supplies default arguments that docker run arguments replace. The command the container actually runs depends on which form each instruction uses:
No ENTRYPOINT |
ENTRYPOINT exec_entry p1_entry (shell) |
ENTRYPOINT ["exec_entry", "p1_entry"] (exec) |
|
|---|---|---|---|
No CMD |
Error: no command specified | /bin/sh -c exec_entry p1_entry |
exec_entry p1_entry |
CMD ["exec_cmd", "p1_cmd"] |
exec_cmd p1_cmd |
/bin/sh -c exec_entry p1_entry |
exec_entry p1_entry exec_cmd p1_cmd |
CMD exec_cmd p1_cmd |
/bin/sh -c exec_cmd p1_cmd |
/bin/sh -c exec_entry p1_entry |
exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
Three rules follow from the table:
- Use the exec form for both instructions. Shell-form
ENTRYPOINTdropsCMD, and shell-formCMDunder an exec-formENTRYPOINTpasses/bin/sh -c …as literal arguments. - Setting
ENTRYPOINTresets anyCMDinherited from the base image. DeclareCMDagain after it if you want default arguments. - For the
CMDside of this — defaults, and how Compose and Kubernetes override them — see the CMD reference.
Passing arguments and overriding ENTRYPOINT
Given this Dockerfile:
FROM alpine:3.20
ENTRYPOINT ["ping", "-c", "3"]
CMD ["localhost"]
# No arguments: ENTRYPOINT + CMD
docker run --rm pinger
# runs: ping -c 3 localhost
# Arguments after the image name replace CMD and are appended to ENTRYPOINT
docker run --rm pinger 1.1.1.1
# runs: ping -c 3 1.1.1.1
# --entrypoint replaces the executable; arguments still go after the image name
docker run --rm --entrypoint /bin/sh pinger -c "ping -c 1 localhost"
# runs: /bin/sh -c "ping -c 1 localhost"
# Open a shell for debugging, ignoring the entrypoint
docker run --rm -it --entrypoint sh pinger
--entrypoint accepts only the executable, not a full command line: --entrypoint "sh -c ls" looks for a binary literally named sh -c ls. Overriding the entrypoint also discards the image's CMD, so pass any arguments explicitly. --entrypoint "" clears the entrypoint entirely.
The equivalents elsewhere: Docker Compose uses entrypoint:, and Kubernetes uses the container's command: field (Kubernetes args: maps to CMD).
Entrypoint scripts, PID 1 and exec "$@"
When the container needs setup at start time (render a config file, wait for a dependency, fix permissions on a volume), use a script as the ENTRYPOINT and pass the real command as CMD:
#!/bin/sh
# docker-entrypoint.sh
set -e
# Start-up tasks go here, e.g. fail fast on missing config
: "${DATABASE_URL:?DATABASE_URL must be set}"
mkdir -p /tmp/app-cache
# Replace this shell with CMD (or docker run arguments)
exec "$@"
FROM alpine:3.20
COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
COPY --chmod=755 server /usr/local/bin/server
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["server", "--port", "8080"]
"$@" expands to the script's arguments — the CMD array, or whatever was passed to docker run — with each argument quoted individually. exec replaces the shell process with that command, so the application becomes PID 1 and receives SIGTERM from docker stop. Without exec, the shell stays PID 1, the signal never reaches your application, and the container is killed after the stop timeout.
This pattern also keeps the image flexible: docker run myimage sh still runs the setup, then drops into a shell instead of the server.
Two more PID 1 details:
- The Linux kernel does not apply default signal actions to PID 1. A program that installs no
SIGTERMhandler will ignore it even in exec form. - PID 1 is responsible for reaping zombie child processes. If your application spawns children and does not reap them, run with
docker run --init(or addtinias the entrypoint), which inserts a minimal init that forwards signals and reaps zombies.
To send a signal other than SIGTERM on stop, set STOPSIGNAL in the Dockerfile.
Examples
Container as a CLI tool
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY cli.py .
ENTRYPOINT ["python", "cli.py"]
CMD ["--help"]
docker run mytool prints help; docker run mytool convert in.csv runs python cli.py convert in.csv.
Fixed executable, overridable flags
FROM nginx:1.27-alpine
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
Runs nginx -g "daemon off;" by default. docker run myimage -t runs nginx -t to test the configuration. Note this replaces the official image's own /docker-entrypoint.sh, which runs the scripts in /docker-entrypoint.d/.
Clearing an inherited ENTRYPOINT
FROM some/base-with-entrypoint
ENTRYPOINT []
CMD ["/usr/local/bin/worker"]
An empty array removes the base image's entrypoint so CMD runs on its own.
Notes
- Only the last
ENTRYPOINTinstruction takes effect, including one inherited from the base image. ENTRYPOINTruns when the container starts, not duringdocker build. Use RUN for build-time commands.- Build-time
ENVandARGsubstitution does not apply toENTRYPOINT; variables are only expanded at run time, and only by a shell. - In exec form on Windows, backslashes must be escaped:
["c:\\app\\app.exe"]. - Inspect the effective values with
docker image inspect --format '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' myimage.
FAQ
What do the square brackets [] mean in a Dockerfile ENTRYPOINT?
They mark the exec form. The value is parsed as a JSON array, so every element must be wrapped in double quotes: ENTRYPOINT ["python", "app.py"]. Docker runs the first element directly with the rest as arguments, without a shell. If the array is not valid JSON (for example, it uses single quotes), Docker silently treats the whole line as shell form. An empty array, ENTRYPOINT [], clears an ENTRYPOINT inherited from the base image.
How do I pass arguments to a Docker ENTRYPOINT?
Put them after the image name: docker run myimage --port 9000. With an exec-form ENTRYPOINT, those arguments are appended to it and replace the image's CMD entirely. Shell-form ENTRYPOINT ignores them.
How do I override the ENTRYPOINT when running a container?
Use docker run --entrypoint. The flag takes only the executable; arguments go after the image name: docker run --entrypoint /bin/sh myimage -c "ls /app". Overriding the entrypoint also discards the image's CMD. In Compose use entrypoint:, in Kubernetes use command:.
Why doesn't $VAR expand in my exec-form ENTRYPOINT?
Variable expansion is done by a shell, and the exec form does not start one. Either call a shell explicitly, ENTRYPOINT ["sh", "-c", "exec myapp --port \"$PORT\""], or read the variable inside an entrypoint script that ends with exec "$@".
Why does my container take 10 seconds to stop?
docker stop sends SIGTERM to PID 1 and sends SIGKILL after a 10-second grace period. If PID 1 is a shell (shell-form ENTRYPOINT, or a script without exec) or an application with no SIGTERM handler, the signal is ignored and the container is killed at the timeout. Use the exec form, end scripts with exec "$@", or run with docker run --init.