Docker CMD vs ENTRYPOINT: Differences Explained

By 

•

Published on

•

8 min read

Docker ENTRYPOINT executable block beside replaceable CMD argument cards

When you run a Docker image, you may want to start its usual application, pass different arguments, or open a shell. Whether those arguments replace the startup command or become part of it depends on two Dockerfile instructions: CMD and ENTRYPOINT.

CMD provides a default command or arguments that you can replace at runtime. An exec-form ENTRYPOINT defines the executable and its fixed arguments, and Docker appends runtime arguments to that list. This guide explains what each instruction does, how they interact when both are present, and which one to use in your own images.

To follow the examples, you need Docker installed and running. Save the example files in a new directory and run the build commands from that directory.

What CMD Does

Without an ENTRYPOINT, CMD sets the command a container runs when you do not specify one on the command line. In the exec form, it is written as a JSON array. Save this example as Dockerfile.cmd:

Dockerfile.cmddockerfile
FROM ubuntu:26.04
CMD ["echo", "Hello from CMD"]

Build the image and run it without a command after the image name:

Terminal
docker build -f Dockerfile.cmd -t cmd-demo .
docker run --rm cmd-demo

The container prints the message supplied by CMD:

output
Hello from CMD

Now pass a different command after the image name:

Terminal
docker run --rm cmd-demo ls /etc

The container lists /etc instead of printing the message. Anything after the image name replaces the whole CMD, not just part of it. You can swap the default for another command without rebuilding the image. The --rm flag removes each example container after it exits.

A Dockerfile can contain only one effective CMD. If the instruction appears several times, only the last one counts. Without a new CMD or ENTRYPOINT, the image inherits its base image’s startup configuration. Defining a new ENTRYPOINT clears an inherited CMD, so you need to set CMD again if you want default arguments.

What ENTRYPOINT Does

An exec-form ENTRYPOINT sets the executable and any fixed arguments. Arguments after the image name are appended to it. To try this, save the following as Dockerfile:

Dockerfiledockerfile
FROM ubuntu:26.04

RUN apt-get update && \
    apt-get install -y --no-install-recommends iputils-ping && \
    rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["ping", "-c", "3"]

The Ubuntu image does not include ping, so the RUN instruction installs it. Build the image, then supply a destination when you start the container:

Terminal
docker build -t pinger .
docker run --rm pinger 127.0.0.1

The container executes ping -c 3 127.0.0.1 and sends three requests to its own loopback interface. Using a loopback address keeps this example independent of DNS and access to external hosts. Without a destination, docker run --rm pinger fails because ping requires one.

Because the entrypoint is meant to be fixed, docker run arguments cannot override it. Replacing it requires the explicit --entrypoint flag:

Terminal
docker run --rm -it --entrypoint /bin/sh pinger

This starts an interactive shell instead of ping. Type exit to leave the shell. The flag accepts only the executable; any arguments for it still go after the image name. Setting --entrypoint also clears the image’s default CMD.

Using CMD and ENTRYPOINT Together

You can combine the two instructions to give the container a default behavior and still let users change its arguments. Replace the previous Dockerfile with this version:

Dockerfiledockerfile
FROM ubuntu:26.04

RUN apt-get update && \
    apt-get install -y --no-install-recommends iputils-ping && \
    rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["ping"]
CMD ["-c", "3", "127.0.0.1"]

Here, ENTRYPOINT fixes only the executable. The request count and destination are in CMD, so you can replace them together. Rebuild the image and run it with its defaults:

Terminal
docker build -t pinger .
docker run --rm pinger

The container runs ping -c 3 127.0.0.1. To send a single request instead, supply the replacement arguments:

Terminal
docker run --rm pinger -c 1 127.0.0.1

The container runs ping -c 1 127.0.0.1. All of CMD was replaced, so you must supply both the count and the destination. Running docker run --rm pinger 127.0.0.1 would remove the count limit and leave ping running until you stop it.

The following table shows common combinations. The shell-form row assumes the default Linux shell:

ENTRYPOINTCMDdocker run argumentsCommand that runs
none["echo", "hello"]noneecho hello
none["echo", "hello"]ls /etcls /etc
["ping"]["-c", "3", "127.0.0.1"]noneping -c 3 127.0.0.1
["ping"]["-c", "3", "127.0.0.1"]-c 1 127.0.0.1ping -c 1 127.0.0.1
echo hello (shell form)anyany/bin/sh -c 'echo hello'; additional arguments are unused

The last row deserves attention: a shell-form ENTRYPOINT does not pass CMD or runtime arguments to the command in the usual way. This is one reason to use the exec form when combining these instructions. Docker’s Dockerfile reference includes the full interaction table.

Shell Form vs Exec Form

Both instructions accept two syntaxes. The exec form is the JSON array used above, and it runs the program directly. The shell form is plain text:

dockerfile
CMD echo "$HOME"

On Linux, the shell form uses /bin/sh -c by default. A Dockerfile can change that shell with the SHELL instruction. Shell processing allows variable expansion, so the example above prints the container’s home directory. In the exec form there is no shell, and CMD ["echo", "$HOME"] prints the literal string $HOME.

When you need shell expansion in an exec-form instruction, invoke the shell explicitly:

dockerfile
CMD ["sh", "-c", "exec echo \"$HOME\""]

The shell expands $HOME, then exec replaces the shell with echo. The same approach is useful when a long-running application needs values expanded before it starts.

An extra shell also affects shutdown. docker stop sends the configured stop signal, normally SIGTERM, to the container’s main process, PID 1. A shell that remains as PID 1 does not automatically forward that signal to its child. If the application keeps running, Docker sends SIGKILL after the timeout, which defaults to 10 seconds for Linux containers unless configured otherwise.

A common way to perform startup work before launching an application is an entrypoint script that ends with exec "$@". Save this script as docker-entrypoint.sh:

docker-entrypoint.shsh
#!/bin/sh
set -e

# Add any startup setup here.
exec "$@"

Copy the script into an image and give it executable permissions. Save the following as Dockerfile.entrypoint:

Dockerfile.entrypointdockerfile
FROM ubuntu:26.04
COPY --chmod=755 docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["sleep", "3600"]

The script receives the CMD (or the docker run arguments) as "$@", performs its setup, and then exec replaces the shell with the requested program. That program becomes PID 1 and can receive the stop signal directly. The application still needs to handle the signal correctly for a graceful shutdown.

The sleep command in this example has no SIGTERM handler, and the kernel ignores such signals for PID 1. Stopping the default container therefore takes the full 10 seconds. When an application does not handle signals itself, add --init to docker run (or init: true in Compose). Docker then starts a small init process as PID 1 that forwards the signal to your command.

Build the image and replace its default sleep command with a short command:

Terminal
docker build -f Dockerfile.entrypoint -t startup-demo .
docker run --rm startup-demo echo "Ready"

The script runs exec echo "Ready", so the container prints Ready and exits. In your own image, replace the example sleep command with your application.

Overriding Both in Docker Compose

Compose has a field for each instruction: command: replaces CMD and entrypoint: replaces ENTRYPOINT. For the combined pinger image built earlier, save this file as compose.yaml:

compose.yamlyaml
services:
  pinger:
    image: pinger
    command: ["-c", "1", "127.0.0.1"]

The entrypoint remains ping, so this service runs ping -c 1 127.0.0.1. Start a temporary container with:

Terminal
docker compose run --rm pinger

You will see one ping reply before the container exits. To replace the executable as well, change compose.yaml to set both fields:

compose.yamlyaml
services:
  pinger:
    image: pinger
    entrypoint: ["/bin/echo"]
    command: ["Entrypoint replaced"]

Running the same Compose command now prints Entrypoint replaced. A non-null entrypoint: causes Compose to ignore the image’s default CMD, so supply command: when the replacement executable needs arguments. Use null to keep an image default, or an empty list ([]) to clear it.

Unlike a Dockerfile’s shell-form CMD, a Compose command: string does not automatically run through a shell. If you need shell expansion or operators such as &&, invoke /bin/sh -c explicitly. See the Compose command reference for details.

For more runtime options, see our docker run command guide . Our Docker Compose guide covers defining and running multiple services.

Choosing Between CMD and ENTRYPOINT

Use CMD alone when the image is a general-purpose environment and users are expected to run arbitrary commands. Use ENTRYPOINT alone when the container wraps a single tool and users will supply its arguments.

Use both when you want a fixed executable with replaceable default arguments, as in the combined ping example. Put only the executable and fixed arguments in ENTRYPOINT; put the defaults you want users to change in CMD.

An entrypoint script ending in exec "$@" serves a different purpose: it performs setup before running the command supplied by CMD or the user. The executable is replaceable in that pattern. Whichever approach you choose, prefer the exec form and check how your application handles the container’s stop signal.

Conclusion

Keep changeable defaults in CMD, and use an exec-form ENTRYPOINT when the image should behave like a specific command. To see where these instructions fit in a complete build, read How to Build Docker Images with Dockerfile .

Tags

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page