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→ runsnode server.jsdocker run my-image app.js→ runsnode app.jsdocker run --entrypoint sh my-image→ runssh(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
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.