ENTRYPOINT vs CMD: Who Actually Runs in Your Container
ENTRYPOINT sets the executable, CMD sets the default arguments — and the rules for how they combine, override, and break signal handling are the ones every Docker user eventually needs.
Published on • October 13, 2026
AI Assistant

Every Dockerfile tutorial shows CMD ["nginx", "-g", "daemon off;"] and moves on. Then one day you write ENTRYPOINT ["python", "app.py"] and CMD ["--dev"], run docker run myimage --prod, and wonder why the container ignored you — or why docker stop takes ten seconds and then kills your process.
ENTRYPOINT and CMD are the two instructions that decide what a container runs. They interact by a handful of precise rules, and the reason they exist at all becomes obvious once you see how they compose.
The one-sentence version
ENTRYPOINTmakes the container behave like an executable: it defines the program that always runs.CMDprovides the default arguments for that executable, or the default command when there is no entrypoint.
Put them together and the container’s startup command is ENTRYPOINT + CMD. That composition is the entire design.
The interaction matrix
The Dockerfile reference lays out the rules; here they are condensed into what you actually need:
| ENTRYPOINT | CMD | Container runs |
|---|---|---|
| exec form | — | just the entrypoint |
| — | exec form | just the CMD |
| exec form | exec form | entrypoint + CMD as arguments |
| shell form | — | /bin/sh -c <entrypoint> |
| — | shell form | /bin/sh -c <cmd> |
And the override rules at run time:
docker run myimage --prod→ replaces CMD, appends your args to ENTRYPOINT.docker run --entrypoint /bin/sh myimage→ replaces ENTRYPOINT entirely.
So with ENTRYPOINT ["python", "app.py"] and CMD ["--dev"], the container starts python app.py --dev; docker run myimage --prod starts python app.py --prod. This is why entrypoint-plus-CMD is the standard shape for CLI tools packaged as images: the image is the tool, and users pass normal arguments.
Two more mechanical rules that surprise people:
- Only the last
ENTRYPOINTand lastCMDin a Dockerfile take effect. ACMDin a base image is silently overridden by yourCMD— and if you define anENTRYPOINTwhile inheriting a baseCMD, you may be inheriting arguments you never saw. Check both when debugging an inherited image. - The exec form (
["a","b"]) bypasses the shell. No|, no$VARexpansion, no signal-wrapping. That is a feature for signals (below) and a footgun for environment variables.
Why exec form matters: signals and PID 1
When you run docker stop, the daemon sends SIGTERM to the container’s PID 1 and waits (10s by default) before SIGKILL. What is PID 1 depends on the form:
# shell form — PID 1 is /bin/sh, your app is its child
CMD python app.py
# exec form — PID 1 IS your app
CMD ["python", "app.py"]
In the shell form, sh forwards nothing; your Python app never sees SIGTERM, doesn’t flush or close connections, and gets hard-killed ten seconds later. Two mitigations:
- Prefer the exec form — it is the default recommendation in the Dockerfile reference for exactly this reason.
- If your app genuinely needs a shell feature, use
sh -c 'exec python app.py'—execreplaces the shell with your app, restoring correct signal delivery.
This interacts directly with your restart policy (more on those here): an app that ignores SIGTERM also looks like a crash-looping app to an orchestrator doing rolling restarts.
The recipes
Service image (web server, database):
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
The entrypoint script does setup (env validation, migrations, config templating) then exec "$@", handing off to CMD. exec is mandatory — without it your entrypoint script stays as PID 1.
CLI tool image:
ENTRYPOINT ["docker-migrate"]
CMD ["--help"]
Users run docker run migrate tool --database-url .... Clean CLI ergonomics with zero extra work.
One-shot task:
CMD ["python", "jobs/backfill.py"]
CMD alone is fine when there is nothing to compose — jobs, migrations, build steps.
The debug escape hatch. An image with a hardcoded entrypoint can always be short-circuited:
docker run -it --entrypoint /bin/sh myimage
Worth knowing when a production container is misbehaving and you need a shell now.
Common failure modes
- “My CMD arguments are ignored.” You have an ENTRYPOINT, and your
docker runargs replaced CMD — check what the entrypoint actually receives withdocker inspect --format '{{.Config.Entrypoint}} {{.Config.Cmd}}' myimage. - “The container runs but exits instantly.” ENTRYPOINT + CMD composed into a command that does not exist (
ENTRYPOINT ["python"]+ inherited baseCMD ["nginx"]→python nginx). Inspect both values. - “Env vars don’t expand in CMD.” Exec form does not invoke a shell. Either switch to shell form (losing signal handling) or do expansion in an entrypoint script.
- “Stop takes 10 seconds every time.” Shell-form entrypoint or missing
exec— fix PID 1.
ENTRYPOINT, CMD, and multi-stage builds
In multi-stage builds (covered in Docker multi-stage builds), only the final stage’s ENTRYPOINT/CMD matter — but people often copy the builder stage wholesale and inherit a CMD intended for the build step. Pin the runtime command explicitly in the final stage even when it “looks” right from the base image.
Further reading
- Dockerfile reference — ENTRYPOINT and CMD — the authoritative rules, including the full override matrix
- Dockerfile best practices — exec-form guidance and layer hygiene
- Docker restart policies explained — how startup commands interact with restart behavior
- cgroups v2: container resource limits — the other half of container runtime behavior
CMD is a default you expect users to override. ENTRYPOINT is a contract you expect them to keep. Getting that instinct right — executable-ness in ENTRYPOINT, flexibility in CMD, exec form for signals — eliminates an entire category of “why did my container do that” hours.