✦ For everyone, free.

Practical knowledge for real and everyday life

Home

20.1.2.4 First Command Definition

A focused guide to First Command Definition, connecting core concepts with practical Docker and container operations.

Every Docker container runs a process. The Dockerfile's CMD and ENTRYPOINT instructions define which process that is and how it starts. Understanding the difference between them, the two syntactic forms each takes, and how they interact is essential for writing containers that start correctly and behave predictably.

The Role of CMD

CMD defines the default command that runs when a container is started with docker run and no command is supplied on the command line. It is the container's default behavior.

FROM ubuntu:22.04
CMD ["echo", "hello from the container"]
docker run my-image

Output: hello from the container

If a command is provided to docker run, it overrides CMD entirely:

docker run my-image ls /etc

The container runs ls /etc instead of echo hello from the container. This makes CMD a default that users can easily override.

The Exec Form and the Shell Form

Both CMD and ENTRYPOINT have two syntactic forms with different behaviors.

Exec form — a JSON array of strings:

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

Docker starts the process directly with the given arguments. There is no shell involved. The process receives PID 1 inside the container. Signals sent to the container (like SIGTERM when docker stop is called) go directly to the process, allowing it to shut down gracefully.

Shell form — a plain string:

CMD node server.js

Docker wraps this in /bin/sh -c "node server.js". The shell process becomes PID 1; the application is a child of the shell. When Docker sends SIGTERM to PID 1 (the shell), the shell does not forward the signal to the child process by default. The application may not receive the shutdown signal, causing it to be killed abruptly after the stop timeout.

For production containers, the exec form is strongly preferred because it handles signals correctly.

The Role of ENTRYPOINT

ENTRYPOINT defines the fixed executable that always runs, regardless of what is provided to docker run. Arguments passed on the command line are appended to the ENTRYPOINT command rather than replacing it.

FROM alpine:3.19
ENTRYPOINT ["ping"]
CMD ["-c", "4", "8.8.8.8"]
docker run my-ping

Runs: ping -c 4 8.8.8.8 (ENTRYPOINT + CMD)

docker run my-ping -c 10 1.1.1.1

Runs: ping -c 10 1.1.1.1 (ENTRYPOINT + the overriding CMD from the command line)

The ENTRYPOINT stays fixed; what changes is the argument list.

CMD and ENTRYPOINT Together

When both are defined in exec form, ENTRYPOINT is the executable and CMD provides its default arguments:

ENTRYPOINT ["node"]
CMD ["server.js"]
  • docker run my-image → runs node server.js
  • docker run my-image app.js → runs node app.js
  • docker run --entrypoint sh my-image → runs sh (overrides ENTRYPOINT)

This pattern is common for images that wrap a single tool and want users to be able to pass custom arguments without specifying the tool name each time.

Combining Forms: Mixed Behavior

If ENTRYPOINT uses the shell form and CMD uses the exec form, or vice versa, the behavior changes:

ENTRYPOINT node          # shell form → /bin/sh -c "node"
CMD ["server.js"]        # exec form → passed as array

Shell form ENTRYPOINT ignores CMD entirely, because /bin/sh -c "node" does not accept arguments appended from CMD. Use matching forms — both exec or both shell — to get predictable behavior.

ENTRYPOINT as a Wrapper Script

A common pattern is to use a shell script as the ENTRYPOINT to perform initialization before starting the application. The script ends by exec-ing the application with "$@" to forward any arguments and hand over PID 1:

#!/bin/sh
# entrypoint.sh
echo "Starting up..."
exec "$@"
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "server.js"]

Running docker run my-image node other.js calls entrypoint.sh with arguments node other.js, which the script passes to exec. The exec call replaces the shell process with node other.js, so Node.js becomes PID 1 and receives signals correctly.

Only the Last CMD and ENTRYPOINT Apply

A Dockerfile can contain multiple CMD and ENTRYPOINT instructions, but only the last one in the file takes effect. Earlier ones are silently ignored. This matters in multi-stage builds or when extending base images that already define a CMD:

FROM node:20-alpine
# node:20-alpine defines CMD ["node"]
COPY server.js .
CMD ["server.js"]       # overrides node:20-alpine's CMD

The final image's default command is node server.js — Docker combines the ENTRYPOINT from the base image (node) with the new CMD (["server.js"]).

Overriding at Runtime

Both CMD and ENTRYPOINT can be overridden at docker run time:

Override CMD:

docker run my-image custom-arg

Override ENTRYPOINT:

docker run --entrypoint /bin/sh my-image

Override both:

docker run --entrypoint /bin/sh my-image -c "echo override"

Summary of Behaviors

Defined docker run Result CMD only no command CMD runs CMD only with command command replaces CMD ENTRYPOINT only no command ENTRYPOINT runs alone ENTRYPOINT only with command ENTRYPOINT + command args ENTRYPOINT + CMD no command ENTRYPOINT + CMD args ENTRYPOINT + CMD with command ENTRYPOINT + command args

Practical Recommendations

Use CMD alone when the image wraps an application whose command and arguments should be easily customizable. Use ENTRYPOINT (exec form) alone when the container always runs a specific executable and the arguments are the variable part. Use both together when the executable is fixed and the default arguments should be overridable. Use a wrapper shell script as ENTRYPOINT when initialization steps (environment setup, config rendering) must run before the application starts.