Docker Best Practices: Images

Grigor Khachatryan
devgorilla
Published in
4 min readNov 13, 2017

--

Understand build context

When you issue a docker build command, the current working directory is called the build context. To exclude files not relevant to the build (without restructuring your source repository) use a .dockerignore file. This file supports exclusion patterns similar to .gitignore files. For information on creating one, see the .dockerignore file.

Non-root user

By default, Docker runs container as root which inside of the container can pose as a security issue. You would want to run the container as an unprivileged user wherever possible.

Minimizing number of layers

Try to reduce the number of layers that will be created in your Dockerfile. The instructions RUN, COPY, ADD create layers. Other instructions create temporary intermediate images, and do not directly increase the size of the build.

Example :

Suppose you need to get a zip file and extract it and remove the zip file. There are two possible ways to do this.

COPY <filename>.zip <copy_directory>
RUN unzip <filename>.zip
RUN rm <filename>.zip

or in one RUN block:

RUN curl <file_download_url> -O <copy_directory> \
&& unzip <copy_directory>/<filename>.zip -d <copy_directory> \
&& rm <copy_directory>/<filename>.zip

The first method will create three layers and will also contain the unwanted .zip in the image which will increase the image size as well. However, the second method only creates a single layer and is thus preferred as the optimum method, as long as minimizing the number of layers is the highest priority. It has the drawback, however, that changes to any one of the instructions will cause all instructions to execute again — something the docker build cache mechanism will avoid. Choose the strategy that works best for your situation.

For more detailed information see Best practices for writing Dockerfiles.

However, sometimes executing all commands in one RUN block can make the script more opaque, especially when trying to mix and match && and || statements. An alterative syntax is to use line continuation as you normally would, but explicitly switch on the shell's "exit on error" mode.

RUN set -e ;\
echo 'successful!' ;\
echo 'but the next line will exit: ' ;\
false ;\
causing this line not to run
# now you can use traditional shell flow of control without worry:
RUN set -e ;\
echo 'next line will take evasive action' ;\
if false; then \
echo 'it seems that was false' >&2 ;\
fi ;\
echo 'and the script continues'

Use multi-stage builds

With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image.To show how this works, let’s take a look at the Dockerfile:

Minimizing layer size

Some installations create data that isn’t needed. Try to remove this unnecessary data within layers:

RUN yum install -y epel-release && \
rpmkeys --import file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 && \
yum install -y --setopt=tsflags=nodocs bind-utils gettext iproute\
v8314 mongodb24-mongodb mongodb24 && \
yum -y clean all

For more detailed information, see container best practices:

Avoid npm and pm2 in CMD

When creating an image, you can bypass the package.json’s start command and bake it directly into the image itself. First off this reduces the number of processes running inside of your container. Secondly it causes exit signals such as SIGTERM and SIGINT to be received by the Node.js process instead of npm swallowing them.

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

RUN-only environment variables

If one needs an environment variable set during a RUN block, but it is either unnecessary, or potentially disruptive to downstream images, then one can set the variable in the RUN block instead of using ENV to declare it globally in the image:

RUN export DEBIAN_FRONTEND=noninteractive ;\
apt-get update ;\
echo and so forth

Tagging

Use tags to reference specific versions of your image.

Tags could be used to denote a specific Docker container image. Hence, the tagging strategy must include a unique counter like build id from a CI server (e.g. Jenkins) to help with identifying the correct image.

For more detailed information see The tag command.

Log Rotation

Use --log-opt to allow log rotation. This helps if the containers you are creating are too verbose and are created too often due to a continuous deployment process.

For more detailed information, see the log driver options.

Like to learn?

Follow me on twitter where I post all about the latest and greatest AI, DevOps, VR/AR, Technology, and Science! Connect with me on LinkedIn too!

--

--