Advanced Entrypoint Techniques for Docker Container

Building good images can be challenging. We want to provide enough abstraction and flexibility for the image to be used in different scenarios without having to rebuild them in every case. We also want to make it easy for users to use the image.

CMD

When building docker images, it is oftentimes enough to use normal commands to start the container.

FROM alpine
CMD ["echo", "Hello, world!"]

Entrypoint

Occasionally, an additional entry point is used. When written like this, it is mostly for convenience, but there are some elaborate use cases.

FROM alpine
ENTRYPOINT ["echo"]
CMD ["Hello, world!"]

Entrypoint Script

Sometimes, we really need to do more work or operate on environment variables, which can be tricky due to the difference in shell and exec syntax in the Dockerfile. In that case, a script commonly called entrypoint.sh or docker-entrypoint.sh is executed as entrypoint.

FROM alpine
ENV WORKER_SLEEP=5
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
#!/bin/sh
set -e
echo 'Work, Work!'
sleep $WORKER_SLEEP
echo 'Hello, World!'

The set -e tells the shell to abort on first error. You can also set the -x flag to see the execution plan.

Entrypoint Script with CMD

It is possible to still take the command list as arguments and access them in the script with the standard shell variables. Here, the CMD is executed via exec [email protected] at the end of the entrypoint.sh.

FROM alpine
ENV WORKER_SLEEP=5
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
CMD echo 'Hello, World!'
#!/bin/sh
set -e
echo 'Work, Work!'
sleep $WORKER_SLEEP
exec [email protected]

[email protected] stands for the complete argument list. While the individual positional arguments can be accessed by $n where n stands for the arguments position in the list. e.g. $1, $2.

Another benefit is that the final application will become PID 1.

This script uses the exec Bash command so that the final running application becomes the container’s PID 1. This allows the application to receive any Unix signals sent to the container. For more, see the ENTRYPOINT reference.

Preserving Entrypoint Behavior

The argument list can be used in different way. So it doesn’t only work with exec. Below the arguments are used like in the early entry point example but only after performing the work.

FROM alpine
ENV WORKER_SLEEP=5
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT [ "/entrypoint.sh" ]
CMD ["Hello, World!"]
#!/bin/sh
set -e
echo 'Work, Work!'
sleep $WORKER_SLEEP
echo "[email protected]"

Note how instead of exec [email protected] it is echo [email protected] so anything that was passed to as command will get echoed. You can also use exec echo "[email protected]" which would run the echo command with PID 1 again.

Shifting the Arguments

When building software for other people to use, it is always a good idea to anticipate all sorts of misuse. With entrypoint-scripts, it can be a good idea to leverage shift to correct the users mistakes.

The Dockerfile stays the same, but now we are checking if the first argument is echo and if so we remove if by shifting the argument list. Shift removes the first argument from the left and shifts the indices of the remaining arguments to the left.

#!/bin/sh
set -e
echo 'Work, Work!'
sleep $WORKER_SLEEP
[[ "$1" == "echo" ]] && shift
echo "[email protected]"

That way, the user can run the image both ways.

docker run my-image Hello, World! # or
docker run my-image echo Hello, World!

Real World Example

You can look at various official docker repos. For example, the official nginx docker image on GitHub.

Nginx Entrypoint
#!/bin/sh
# vim:sw=4:ts=4:et

set -e

if [ -z "${NGINX_ENTRYPOINT_QUIET_LOGS:-}" ]; then
exec 3>&1
else
exec 3>/dev/null
fi

if [ "$1" = "nginx" -o "$1" = "nginx-debug" ]; then
if /usr/bin/find "/docker-entrypoint.d/" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
echo >&3 "$0: /docker-entrypoint.d/ is not empty, will attempt to perform configuration"

echo >&3 "$0: Looking for shell scripts in /docker-entrypoint.d/"
find "/docker-entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do
case "$f" in
*.sh)
if [ -x "$f" ]; then
echo >&3 "$0: Launching $f";
"$f"
else
# warn on shell scripts without exec bit
echo >&3 "$0: Ignoring $f, not executable";
fi
;;
*) echo >&3 "$0: Ignoring $f";;
esac
done

echo >&3 "$0: Configuration complete; ready for start up"
else
echo >&3 "$0: No files found in /docker-entrypoint.d/, skipping configuration"
fi
fi

exec "[email protected]"

Another interesting one is from mongo db. It is fairly complex, with almost 400 lines. You can see the full script on GitHub.

It contains, amongst various others, a function using the shift method described earlier.

Shift Function
# _mongod_hack_ensure_no_arg '--some-unwanted-arg' "[email protected]"
# set -- "${mongodHackedArgs[@]}"
_mongod_hack_ensure_no_arg_val() {
local ensureNoArg="$1"; shift
mongodHackedArgs=()
while [ "$#" -gt 0 ]; do
local arg="$1"; shift
case "$arg" in
"$ensureNoArg")
shift # also skip the value
continue
;;
"$ensureNoArg"=*)
# value is already included
continue
;;
esac
mongodHackedArgs+=( "$arg" )
done
}