Dockerfile CMD vs ENTRYPOINT: The PID 1 Difference

By Anders Vik ·

Dockerfile CMD vs ENTRYPOINT: Which One to Use

Your container takes ten seconds to stop every time you run docker stop, and Ctrl-C in the foreground does nothing at all. Nobody told you that the cause is a missing pair of square brackets. This is the most common way people meet the Dockerfile CMD vs ENTRYPOINT question: not as an abstract "which instruction should I use" puzzle, but as a container that won't shut down cleanly. Once you understand what each instruction does, and why the way you write it matters as much as which one you pick, both the confusion and the ten-second wait go away.

This article covers the difference between Dockerfile CMD and Dockerfile ENTRYPOINT, how to combine them, why exec form beats shell form, and how to override either one from docker run or a compose.yaml file. Three small experiments at the end let you see the behavior yourself instead of taking it on faith.

CMD and ENTRYPOINT: The 30-Second Answer

Think of a container as a sentence with a verb and a default object. ENTRYPOINT is the verb: the program that always runs when the container starts, no matter what. CMD is the default object: the arguments that get handed to that verb, which anyone running the container can swap out.

If a Dockerfile has no ENTRYPOINT, CMD acts as the whole command instead, and docker run can replace it entirely. If a Dockerfile has ENTRYPOINT, CMD supplies default arguments that docker run can still override, but the ENTRYPOINT program itself stays fixed unless you explicitly ask Docker to change it.

Both instructions accept two different forms, and this is where most of the real trouble starts. Exec form looks like a JSON array: CMD ["node", "server.js"]. Shell form looks like a plain command: CMD node server.js. They are not interchangeable, and the difference decides whether your container responds to docker stop in one second or in ten.

What CMD does

CMD sets the default command (or default arguments) a container runs when nobody specifies anything on the command line. The official Dockerfile reference defines three forms:

CMD ["executable","param1","param2"]   # exec form, preferred
CMD ["param1","param2"]                # exec form, default args for ENTRYPOINT
CMD command param1 param2              # shell form

Only the last CMD in a Dockerfile counts. If you write three of them while debugging and forget to delete the first two, Docker quietly uses the final one and says nothing about the others. That silence is worth remembering the next time a rebuild "doesn't pick up your change": check whether an earlier CMD is still in the file.

With no ENTRYPOINT set, CMD is the whole show. docker run myimage runs whatever CMD specifies. Add a command after the image name and CMD is replaced outright: docker run myimage sh ignores CMD completely and runs a shell instead.

What ENTRYPOINT does

ENTRYPOINT makes the container behave as though it were that one program. Instead of replacing the command the way CMD does, arguments passed on docker run get appended to the end of the ENTRYPOINT array. This is the pattern behind CLI-style images: an image for a tool like Terraform sets ENTRYPOINT ["/bin/terraform"] so that docker run hashicorp/terraform plan runs /bin/terraform plan, not "plan" as a bare command.

One detail that surprises people who build on top of an existing image: setting ENTRYPOINT in a child Dockerfile resets any CMD the base image defined. If the base image shipped a CMD and you add your own ENTRYPOINT without also writing a new CMD, that inherited default silently disappears, per the Dockerfile reference. Check what you actually get with docker inspect, covered further down, rather than assuming the base image's defaults survived.

Under the hood, both instructions end up populating the Cmd and Entrypoint fields of the container image's config, the same fields defined by the OCI Image Format Specification that every Docker image follows. That's exactly what the docker inspect experiment further down reads back.

Using CMD and ENTRYPOINT together

Most real Dockerfiles use both: ENTRYPOINT for the program, CMD for its default flags. Docker's own get-started guide to writing a Dockerfile frames CMD the same way: it sets the default command a container built from the image runs. Layer ENTRYPOINT on top of that when you want the container to behave like one fixed program instead of a replaceable command.

The exact result depends on which form you used for each instruction. The table below reproduces the interaction rules from the Dockerfile reference, with exec_entry and exec_cmd standing in for whatever binary you actually wrote.

No ENTRYPOINTENTRYPOINT exec_entry p1 (shell)ENTRYPOINT ["exec_entry","p1"] (exec)
No CMDerror, not allowed/bin/sh -c exec_entry p1exec_entry p1
CMD ["exec_cmd","p1"] (exec)exec_cmd p1/bin/sh -c exec_entry p1exec_entry p1 exec_cmd p1
CMD exec_cmd p1 (shell)/bin/sh -c exec_cmd p1/bin/sh -c exec_entry p1exec_entry p1 /bin/sh -c exec_cmd p1

Two traps sit inside that table. First, a shell-form ENTRYPOINT (middle column) ignores CMD entirely: if you write ENTRYPOINT myapp without brackets, any CMD you set underneath is dead code. Second, a shell-form CMD under an exec-form ENTRYPOINT (bottom-right cell) does not run as a shell command at all. It gets passed to your ENTRYPOINT as the literal arguments /bin/sh -c exec_cmd p1, which is almost never what you meant.

Exec form vs shell form: the choice that actually matters

Here is the part the "which instruction" framing skips, and it is more expensive to get wrong than picking CMD over ENTRYPOINT. Shell form, for either instruction, runs your command as a subcommand of /bin/sh -c. Exec form runs it directly.

That distinction decides what becomes process ID 1 inside the container. When Linux starts a container, the first process it launches gets PID 1, and PID 1 has a special job: catching signals sent from the host, such as the SIGTERM that docker stop sends. Most shells do not forward signals to their child processes, so whichever process ends up at PID 1 is the only one guaranteed to hear the request.

With shell form, /bin/sh becomes PID 1, and your actual application runs as a child of that shell. SIGTERM arrives, the shell does not pass it along, and your app never hears the request to shut down. Docker's docker stop command waits out a grace period, ten seconds by default on Linux, then sends SIGKILL and the container dies uncleanly, according to the docker stop reference. That ten-second pause on every deploy is the grace period expiring, not a bug in your app.

Exec form skips the shell. Your application becomes PID 1 directly, receives SIGTERM immediately, and can shut down its database connections, finish an in-flight request, or flush a log buffer before it exits. That is the entire reason the Dockerfile reference describes exec form as "preferred" for both CMD and ENTRYPOINT.

Exec form has one tradeoff worth knowing: no shell processing happens, so environment variable substitution does not work. CMD ["echo", "$HOME"] prints the literal string $HOME. If you genuinely need shell features like variable expansion or piping, write them explicitly: CMD ["sh", "-c", "echo $HOME"]. And because exec form is parsed as a JSON array, it requires double quotes around each word; single quotes will fail to parse.

Three experiments you can run in under a minute

Reading about PID 1 is one thing. Watching it happen is more convincing, and none of these takes more than a minute.

Experiment one: time the shutdown. Build one image with CMD npm start and another with CMD ["node", "server.js"] (adjust to your own app's start command). Run each with docker run -d --name test1 image1, then time docker stop test1 against the wall clock. The shell-form image will hang for close to ten seconds; the exec-form image should stop in well under one.

Experiment two: see CMD get replaced. Run docker run myimage echo hi against an image with no ENTRYPOINT. You'll see "hi" printed, because the trailing command replaced CMD outright. Now try the same thing against an image that does set an ENTRYPOINT, such as the official nginx image: docker run nginx echo hi still runs nginx, because with an ENTRYPOINT present, arguments on the command line get appended to it instead of replacing it.

Experiment three: inspect what an image already ships. Before you assume an image's behavior, ask it directly:

docker inspect --type=image --format='{{json .Config.Entrypoint}}' nginx
docker inspect --type=image --format='{{json .Config.Cmd}}' nginx

Run the same two commands against busybox or hashicorp/terraform and compare. This is the fastest way to answer "what does this base image actually do when I run it" without reading its Dockerfile on GitHub first.

Overriding the command at run time

Once you understand the two instructions, overriding them on the command line follows directly from the rules above. Here's the docker run override command cheat sheet, drawn from the flags documented in the docker run reference:

# Append arguments (only works cleanly with exec-form ENTRYPOINT)
docker run myimage arg1 arg2

# Replace CMD entirely when there is no ENTRYPOINT
docker run myimage sh

# Override ENTRYPOINT itself, and drop the image's CMD
docker run --entrypoint /bin/bash myimage

# Override ENTRYPOINT and still pass your own arguments
docker run --entrypoint /usr/bin/redis-cli myimage --help

# Clear ENTRYPOINT back to nothing, falling through to CMD behavior
docker run --entrypoint="" myimage bash

The Compose specification mirrors this with the command: and entrypoint: keys inside a service:

services:
  web:
    image: myimage:1.4
    entrypoint: ["/usr/local/bin/docker-entrypoint.sh"]
    command: ["node", "server.js"]

command: overrides the image's default CMD, the same way trailing arguments do on docker run. entrypoint: overrides ENTRYPOINT, and once it is set to anything non-null, Compose ignores whatever default command the image shipped with. Set either one to an empty list ([]) to discard the image's default outright.

One quirk worth flagging: Compose's command: does not automatically run inside a shell, so if you need shell features like variable expansion, write the shell explicitly and escape any dollar sign as $$: command: /bin/sh -c 'echo "hello $$HOSTNAME"'. If you're also passing configuration values into that service, the guide to Docker Compose environment variables covers how environment: and env_file: differ from hardcoding values into command:.

The exec "$@" entrypoint script pattern

Plenty of official images don't set ENTRYPOINT to the final binary directly. Instead they point it at a small shell script that handles setup, such as initializing a database on first run, then hands control to the real process. The trick that keeps this pattern safe for signals is the exec shell builtin.

The official Postgres entrypoint script ends with a line like this, simplified for clarity:

#!/usr/bin/env bash
set -e

if [ "${1:0:1}" = '-' ]; then
    set -- postgres "$@"
fi

exec "$@"

Without that final exec, the script itself would stay running as PID 1, and Postgres would become a child process, right back into the same signal problem shell form causes. The exec builtin replaces the shell process with the command that follows it instead of spawning a new one, so Postgres itself takes over PID 1 and starts receiving signals directly. Nginx's official entrypoint follows the same shape: run setup steps conditionally, then hand off with exec "$@".

If that setup script also has to wait on another container, such as a database it depends on, being ready before it runs, pair this pattern with Compose's healthcheck and depends_on settings so the two layers of "wait until ready" logic work together instead of racing each other.

Which one should you use? A decision table

What you're buildingRecommended approach
A CLI tool image (Terraform, redis-cli style)ENTRYPOINT (exec form) set to the binary; CMD for default flags if there are sensible ones
A long-running service (web app, API, database)CMD alone in exec form is often enough; add ENTRYPOINT only if you need setup logic first
An image with setup steps before the main processENTRYPOINT pointing at a script that ends in exec "$@"; CMD supplies the default arguments the script forwards
A base image other people will extendDocument your ENTRYPOINT and CMD clearly; combining both without exec form documented is a pattern the Dockerfile reference says "should rarely be used," unless your users already know it

Checking your Dockerfile before you ship it

If you're writing a Dockerfile from scratch, the Dockerfile generator writes CMD in exec form by default, so the ten-second shutdown problem never gets a chance to happen. If you already have a Dockerfile and aren't sure whether an old CMD or ENTRYPOINT is still in shell form, paste it into the Dockerfile and Compose validator, which flags shell-form lines along with roughly forty other common mistakes. For the Compose side, the site's guide to using Docker Compose covers how command: fits into a full compose.yaml file.

To verify a fix worked, don't just read the Dockerfile again. Rebuild the image, run it, and time the stop:

docker build -t myimage .
docker run -d --name checkstop myimage
time docker stop checkstop

If that returns in under a second instead of ten, your PID 1 is receiving SIGTERM correctly. If you're also checking that a service came back up cleanly after a restart, a plain uptime check against the container's published port, such as the one at httpcheck.tools, confirms the app is actually answering requests again, not just that the process exited.

Frequently Asked Questions

What is the difference between CMD and ENTRYPOINT?

CMD sets the default command or default arguments a container runs, and docker run can replace it completely by adding a command after the image name. ENTRYPOINT sets the fixed program the container always runs; arguments from docker run get appended to it rather than replacing it, unless you explicitly override ENTRYPOINT with the --entrypoint flag. In short, ENTRYPOINT is the verb, CMD is the replaceable default argument.

Can a Dockerfile have both CMD and ENTRYPOINT?

Yes, and this is the pattern the Docker documentation itself recommends: ENTRYPOINT sets the main program, and CMD supplies its default flags. With an exec-form ENTRYPOINT, CMD's contents get appended after the ENTRYPOINT command. Just be careful with the forms: a shell-form ENTRYPOINT ignores CMD entirely, and a shell-form CMD under an exec-form ENTRYPOINT gets passed in as literal /bin/sh -c arguments rather than running as a shell command.

Why should CMD use the exec form?

Exec form (CMD ["executable", "arg"]) runs your process directly as PID 1 inside the container, so it receives SIGTERM immediately when something runs docker stop. Shell form (CMD executable arg) puts /bin/sh -c at PID 1 instead, and most shells don't forward signals to their child processes, so your app never hears the stop request. Docker then waits out the grace period, ten seconds by default on Linux, before force-killing the container with SIGKILL. Exec form avoids that wait and lets your app shut down cleanly.

How do I override ENTRYPOINT at run time?

Pass --entrypoint to docker run, for example docker run --entrypoint /bin/bash myimage. This clears out the image's default CMD as well, so to pass arguments to the new entrypoint, add them after the image name: docker run --entrypoint /usr/bin/redis-cli myimage --help. To reset ENTRYPOINT to nothing and fall back to CMD-style behavior, use docker run --entrypoint="" myimage bash. In a compose.yaml file, set the service's entrypoint: key instead; any non-null value there overrides the image's ENTRYPOINT and its default command.