Skip to content

Dockerfile — CMD vs ENTRYPOINT


You’ve used CMD in Dockerfiles to tell a container what to run. There’s another instruction that looks like it does almost the same thing — ENTRYPOINT — but the difference becomes important the moment you pass something after the image name.

This is a short one: what ENTRYPOINT does differently, and why CMD isn’t always the right choice.

What They Share

Both can define the command that runs when a container starts.

For the basic case, they can look almost identical:

CMD ["nginx", "-g", "daemon off;"]
ENTRYPOINT ["nginx", "-g", "daemon off;"]

Both start nginx when the container runs.

If this were the whole story, there wouldn’t be much reason for two instructions.

What ENTRYPOINT Does Extra

ENTRYPOINT makes the container behave like a specific executable.

The important difference appears when you provide arguments at runtime:

ENTRYPOINT ["python", "app.py"]

Now:

docker run myapp --port 8080

effectively runs:

python app.py --port 8080

The arguments are added to the ENTRYPOINT.

With CMD instead:

CMD ["python", "app.py"]

running:

docker run myapp --port 8080

replaces the CMD entirely. Docker tries to run:

--port 8080

which obviously isn’t what you wanted.

That’s the key distinction:

  • CMD provides a default command or default arguments.
  • ENTRYPOINT defines the executable that the container is built to run.

Why CMD Still Wins Sometimes

ENTRYPOINT sounds more intentional, but that doesn’t make it the better choice for every Dockerfile.

If you want the command to be easily replaced:

CMD ["nginx", "-g", "daemon off;"]

is simple and flexible.

You can run:

docker run myapp sh

and get a shell instead.

With:

ENTRYPOINT ["nginx", "-g", "daemon off;"]

the same:

docker run myapp sh

doesn’t replace nginx. Instead, Docker effectively tries to run:

nginx -g "daemon off;" sh

So ENTRYPOINT is useful when the image represents a specific executable, while CMD is useful when you want the runtime command to remain easy to override.

Note

Docker’s usual pattern is to combine them when it makes sense: use ENTRYPOINT for the fixed executable, and CMD for its default arguments. For example: ENTRYPOINT ["python", "app.py"] with CMD ["--port", "8080"]. Then docker run myapp --port 9000 keeps the application but changes its default argument.

How They Actually Combine

The rule most people remember is “use both”, but it’s worth seeing exactly how Docker merges them, because this is where most confusion starts.

    flowchart TD
    A["docker run myapp arg1 arg2"] --> B{Are runtime<br/>args given?}
    B -->|No| C["CMD supplies the default args"]
    B -->|Yes| D["Runtime args replace CMD entirely"]
    C --> E["ENTRYPOINT + CMD (or its replacement)<br/>are joined into one command"]
    D --> E
    E --> F["Final process the container runs"]
  

Only CMD gets replaced by runtime arguments — ENTRYPOINT never does, unless you explicitly override it (more on that below). So with:

ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]
  • docker run myapppython app.py --port 8080 (default CMD used)
  • docker run myapp --port 9000python app.py --port 9000 (CMD replaced)
  • docker run myapp can never accidentally turn into running bash or anything else — ENTRYPOINT is fixed.

Overriding ENTRYPOINT When You Need To

Sometimes you do need to bypass a fixed ENTRYPOINT — for debugging, or to drop into a shell inside an image that wasn’t built with that in mind. Docker lets you do this with the --entrypoint flag:

docker run --entrypoint sh myapp

This replaces ENTRYPOINT for that one run, and any trailing arguments become arguments to the new entrypoint instead of the old one. It’s the escape hatch for images that otherwise never let you get a shell.

docker run --entrypoint sh myapp -c "ls /app"

Note

--entrypoint only accepts a single executable — you can’t pass a full JSON array like in the Dockerfile. If you need ENTRYPOINT ["python", "-m", "http.server"] temporarily replaced, you’d override it with --entrypoint python and then supply -m http.server as trailing arguments.

Exec Form vs Shell Form

Both instructions can be written two ways, and the form you pick changes container behavior beyond just CMD-vs-ENTRYPOINT semantics.

# Exec form — recommended
ENTRYPOINT ["python", "app.py"]

# Shell form
ENTRYPOINT python app.py

The exec form (JSON array) runs the process directly as PID 1. The shell form wraps it inside /bin/sh -c "...", which becomes PID 1 instead, with your actual process running as a child of that shell.

This matters for two reasons:

  • Signal handling. When Docker sends SIGTERM to stop a container, it goes to PID 1. In shell form, the shell receives it — and by default doesn’t forward it to your application. That means docker stop can end up waiting the full timeout and then force-killing with SIGKILL, instead of letting your app shut down gracefully.
  • Variable substitution. Shell form does support environment variable expansion (ENTRYPOINT echo $HOME), which exec form does not do automatically. That’s the one case shell form is actually useful for.
Exec form ["cmd", "arg"]Shell form cmd arg
Runs as PID 1YesNo — the shell is PID 1
Receives signals directlyYesNo, unless the shell forwards them
Environment variable expansionNoYes
Recommended defaultYesOnly when you need shell features

Warning

If you must use shell form and still want clean shutdowns, exec into the process explicitly: CMD exec python app.py. The exec built-in replaces the shell process with your application instead of running it as a child, so it becomes PID 1 after all.

Wrapping Up

CMD gives you a default that can be replaced. ENTRYPOINT gives the container a fixed executable that runtime arguments are appended to, and one that can still be swapped out explicitly with --entrypoint when you need to.

That’s why neither instruction is simply “better.” Use CMD when you want flexibility over what runs, use ENTRYPOINT when the image is meant to behave like a particular executable, and combine them when you want a fixed program with configurable default arguments. Whichever you use, prefer exec form — it’s the difference between a container that shuts down cleanly and one that always needs a hard kill.

Last updated on