Using CMD and ENTRYPOINT in Dockerfiles
Overview
Teaching: 15 min
Exercises: 10 minQuestions
How are default commands set in Dockerfiles?
Objectives
Learn how and when to use
CMD
Learn how and when to use
ENTRYPOINT
So far every time we’ve run the containers we’ve typed
podman run --rm -it <IMAGE>:<TAG> <command>
like
podman run --rm -it python:3.9-slim /bin/bash
Running this dumps us into a Bash session
echo $SHELL
SHELL=/bin/bash
However, if no /bin/bash
is given then you are placed inside the Python 3.9 REPL.
podman run --rm -it python:3.9-slim
Python 3.9.18 (main, Feb 13 2024, 10:56:47)
[GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
These are very different behaviors, so let’s understand what is happening.
The Python 3.9 image has a default command that runs when the container is executed,
which is specified in the Dockerfile with CMD
.
Create a file named Dockerfile.defaults
touch Dockerfile.defaults
# Dockerfile.defaults
# Make the base image configurable
ARG BASE_IMAGE=python:3.9-slim
FROM ${BASE_IMAGE}
USER root
RUN apt-get -qq -y update && \
apt-get -qq -y upgrade && \
apt-get -y autoclean && \
apt-get -y autoremove && \
rm -rf /var/lib/apt-get/lists/*
# Create user "docker"
RUN useradd -m docker && \
cp /root/.bashrc /home/docker/ && \
mkdir /home/docker/data && \
chown -R --from=root docker /home/docker
ENV HOME /home/docker
WORKDIR ${HOME}/data
USER docker
CMD ["/bin/bash"]
Now build the dockerfile, specifying its name with the -f
argument since the engine will otherwise look for a file named Dockerfile
by default.
podman build -f Dockerfile.defaults -t defaults-example:latest .
Now running
podman run --rm -it defaults-example:latest
again drops you into a Bash shell as specified by CMD
.
As has already been seen, CMD
can be overridden by giving a command after the image
podman run --rm -it defaults-example:latest python3
The ENTRYPOINT
builder command allows to define a command or
commands that are always run at the “entry” to the container.
If an ENTRYPOINT
has been defined then CMD
provides optional inputs to the ENTRYPOINT
.
Create a file named entrypoint.sh
# entrypoint.sh
#!/usr/bin/env bash
set -e
function main() {
if [[ $# -eq 0 ]]; then
printf "\nHello, World!\n"
else
printf "\nHello %s\n" "${1}"
fi
}
main "$@"
/bin/bash
And now modify the Dockerfile.defaults
to use the entrypoint.sh
script
# Dockerfile.defaults
# Make the base image configurable
ARG BASE_IMAGE=python:3.9-slim
FROM ${BASE_IMAGE}
USER root
RUN apt-get -qq -y update && \
apt-get -qq -y upgrade && \
apt-get -y autoclean && \
apt-get -y autoremove && \
rm -rf /var/lib/apt-get/lists/*
# Create user "docker"
RUN useradd -m docker && \
cp /root/.bashrc /home/docker/ && \
mkdir /home/docker/data && \
chown -R --from=root docker /home/docker
ENV HOME /home/docker
WORKDIR ${HOME}/data
USER docker
COPY entrypoint.sh $HOME/entrypoint.sh
ENTRYPOINT ["/bin/bash", "/home/docker/entrypoint.sh"]
CMD ["there"]
Note how CMD
provides an optional input to entrypoint.sh
.
podman build -f Dockerfile.defaults -t defaults-example:latest --compress .
So now try
podman run --rm -it defaults-example:latest
Applied
ENTRYPOINT
andCMD
What will be the output of
podman run --rm -it defaults-example:latest $USER
and why?
Solution
Hello <your user name> docker@2a99ffabb512:~/data$
$USER
is evaluated and then overrides the defaultCMD
to be passed toentrypoint.sh
All about
ENTRYPOINT
andCMD
ENTRYPOINT and CMD can be both in “exec” or “shell” form, although we recommend to use exec form. Exec form must be an array of comma separated quoted arguments and it us executed via the Linux
execv()
. E.g.CMD ["/usr/bin/ls", "-al"]
Anything else, also if you forget just the quotes, will be considered shell form, it is passed by Docker/Podman to/bin/sh -c
(as written, with quotes, parentheses, …), and can use shell features like PATH and expansion. E.g.CMD ls -al
At execution, ENTRYPOINT can be overridden with the
--entrypoint
option, CMD with any argument of the invocation. When ENTRYPOINT is in exec form, CMD or the invocatipon arguments are passed as additional arguments (as single string, with additional “/bin/sh” “-c” arguments if CMD is in shell form). When ENTRYPOINT is in shell form, CMD and invocation arguments are ignored.An interactive session,
run -it
, is possible only if the last command (ENTRYPOINT if present, arguments or CMD) is interactive, i.e. not terminating.
The use case seen above is common for application containers: ENTRYPOINT (in exec form) is used for the command and CMD for is ued the default arguments that can be easily overridden at invocation.
Another common use case is to
run an initialization script before anything else in the container, e.g. to download files or set variables only available at run-time, or to
get secrets from a key-store.
For that you can use an entrypoint.sh
like:
#!/bin/sh
echo "You are running on $(hostname)"
# download tokens and recrets
export MY_TOKEN=./token_file.jwt
bash -c "$*"
The last line is the key to treat the arguments in CMD or the command line as commands.
Remember to set entrypoint.sh
as executable and to use the exec form for ENTRYPOINT (ENTRYPOINT ["./entrypoint.sh"]
)
Note that if the file to download or value of the variable are known when building the image, you can use the RUN command in the Dockerfile
instead, which is more efficient than the entrypoint script.
Key Points
CMD
provide defaults for an executing container
CMD
can provide options forENTRYPOINT
ENTRYPOINT
allows you to configure commands that will always run for an executing container