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 $@ 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 $@

$@ 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 "$@"

Note how instead of exec $@ it is echo $@ so anything that was passed to as command will get echoed. You can also use exec echo "$@" 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 "$@"

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 "$@"

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' "$@"
# 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
}