Articles

Dockerfile CMD vs ENTRYPOINT: Differences and When to Use

CMD sets a container's default command or arguments; ENTRYPOINT sets the fixed executable. How CMD and ENTRYPOINT interact, exec vs shell form, and PID 1.

The Lycoris Team The Lycoris Team · · 5 min read
Stacked cargo shipping containers at a port

CMD and ENTRYPOINT are the two Dockerfile instructions that decide what runs when a container starts. ENTRYPOINT defines the executable the container always runs; CMD provides default arguments — or a default command — that a user can easily replace at docker run time. Used together, ENTRYPOINT is the program and CMD is its default arguments. Most confusion about them comes down to how they combine and to the difference between their exec and shell forms.

CMD on its own

CMD sets the default command for a container:

FROM python:3-slim
COPY app.py /app/app.py
CMD ["python", "/app/app.py"]

docker run myimage runs python /app/app.py. But anything you put after the image name replaces CMD entirely:

docker run myimage bash

That starts a shell instead of the app. This makes CMD a good fit for general-purpose images where users will often want to run something else, and it’s why base images like language runtimes typically set a CMD and no ENTRYPOINT.

If a Dockerfile contains several CMD lines, only the last one counts. That includes CMD lines inherited from the base image — your own CMD overrides the parent’s.

ENTRYPOINT on its own

ENTRYPOINT makes the container behave like a specific executable:

FROM alpine
RUN apk add --no-cache curl
ENTRYPOINT ["curl"]

Now arguments passed to docker run are appended to the entrypoint rather than replacing it:

docker run mycurl -sI https://example.com
# runs: curl -sI https://example.com

To replace an entrypoint, you have to say so explicitly with docker run --entrypoint. That extra friction is the point: the image is meant to run one thing.

Using both together

The most useful pattern combines them. ENTRYPOINT fixes the executable, and CMD supplies default arguments that users can override:

ENTRYPOINT ["curl"]
CMD ["--help"]
  • docker run mycurl runs curl --help.
  • docker run mycurl -sI https://example.com runs curl -sI https://example.com.

The arguments after the image name replace CMD, and the result is appended to ENTRYPOINT.

CMD vs ENTRYPOINT compared

CMDENTRYPOINT
PurposeDefault command or default argumentsThe fixed executable
Effect of args after image nameReplaces CMDAppended to ENTRYPOINT
How to overridePass a command to docker rundocker run --entrypoint
Multiple instructionsLast one winsLast one wins
Combined behaviorBecomes ENTRYPOINT’s default argsReceives CMD as arguments
Best forFlexible, general-purpose imagesImages that wrap one tool or app

Exec form vs shell form

Both instructions accept two syntaxes, and the choice matters more than it looks.

Exec form is a JSON array:

ENTRYPOINT ["node", "server.js"]

Docker runs the program directly, with no shell in between. The process becomes PID 1 in the container.

Shell form is a plain string:

ENTRYPOINT node server.js

Docker wraps it as /bin/sh -c "node server.js". That gives you shell features like variable expansion and &&, but it has two serious consequences:

  • Signals don’t reach your app. The shell becomes PID 1, and sh generally doesn’t forward SIGTERM to its child. When docker stop or a Kubernetes rollout asks the container to shut down, the app never hears about it, waits out the grace period, and gets killed with SIGKILL — no graceful shutdown, no draining of in-flight requests.
  • Shell-form ENTRYPOINT ignores CMD and run arguments. Anything you pass is handed to sh -c in a way that never reaches your program.

Use the exec form for both instructions unless you have a specific reason not to. Note that exec form does not expand environment variables like $PORT; if you need that, use a wrapper script (below) rather than shell form.

The PID 1 problem

Even in exec form, your app running as PID 1 has special responsibilities. The kernel doesn’t apply default signal handlers to PID 1, so an app that never installs a SIGTERM handler may ignore it entirely. PID 1 is also expected to reap orphaned child processes.

Two common fixes:

  • Handle SIGTERM explicitly in your application code.
  • Run a tiny init process in front of it, either with docker run --init or by making a minimal init like tini the entrypoint and passing your app as its arguments.

This connects to the isolation model described in cgroups and namespaces: each container gets its own PID namespace, so whatever you start really is process 1 inside it.

The wrapper-script pattern

Many official images use an entrypoint script to do setup before launching the real process:

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["postgres"]
#!/bin/sh
set -e
# one-time setup: render config, wait for dependencies, fix permissions...
exec "$@"

The final exec "$@" matters. It replaces the shell with the command from CMD (or from docker run), so the real process becomes PID 1 and receives signals directly. Forgetting exec reintroduces the signal problem from shell form.

How this maps to Compose and Kubernetes

In Docker Compose, the entrypoint and command keys override ENTRYPOINT and CMD respectively. In Kubernetes the naming is confusingly shifted: a container spec’s command overrides the image’s ENTRYPOINT, and args overrides CMD. Keeping that mapping straight saves a lot of debugging when a pod in a Deployment starts the wrong process.

Practical guidelines

  • Building an image that wraps one tool? Use ENTRYPOINT in exec form, with sensible defaults in CMD.
  • Building a general app image people will poke around in? CMD alone is fine.
  • Need setup logic? Use an entrypoint script that ends in exec "$@".
  • Always prefer exec form, and make sure your app shuts down cleanly on SIGTERM.
  • Keep the final stage lean — multi-stage builds help — so the entrypoint isn’t buried in a heavyweight image. New to all of this? Start with Docker for beginners.

The takeaway

ENTRYPOINT decides what program a container is; CMD decides what it does by default. Arguments to docker run replace CMD and are appended to ENTRYPOINT. Write both in exec form so your process runs as PID 1 and receives shutdown signals, and when you need startup logic, use a wrapper script that hands off with exec "$@".

Chisato Chisato · · 4 min read

Docker vs Podman: Container Runtimes Compared

Docker runs containers through a persistent root daemon; Podman runs them daemonless and rootless by default. What that architectural split changes.

#Docker #DevOps #Containers
The Lycoris Team The Lycoris Team · · 4 min read

Docker Multi-Stage Builds Explained

Multi-stage builds let a Dockerfile use one stage to compile code and a separate, minimal stage to ship it — cutting image size and attack surface.

#Docker #DevOps #Containers