In this blog post, we focus on:

Our goal is to have:

Writing

FROM

Order Matters

for example,

FROM debian
COPY . /app
RUN apt-get update
RUN apt-get install nodejs
CMD ["npm", "start","--prefix","app"]

In above case, everytime you make changes to code and build the image, it will copy the content and run the commands to update & install nodejs which is not ideal.

Instead, what we can do is,

FROM debian
RUN apt-get update
RUN apt-get install nodejs
COPY . /app
CMD ["npm", "start","--prefix","app"]

In this scenario, once you build the image, it will have cache of first 3 layers. whenever you will rebuild the image, it will start from COPY statement and only copy your code into container, hence lesser the image build time.

Chaining

each statement in Dockerfile introduces one extra layer in image, hence we should chain the statement wherever possible

for example, instead of,

RUN apt-get update
RUN apt-get -y install imagemagick curl software-properties-common gnupg vim ssh
RUN apt-get -y install nodejs 

we could write,

RUN apt-get update && apt-get -y install imagemagick curl software-properties-common gnupg vim ssh && apt-get -y install nodejs

This will reduce number of layers in image and hence the size.

Remove Package manager cache

RUN apt-get update apt-get -y install --no-install-recommends \
       imagemagick curl software-properties-common gnupg vim ssh nodejs \
    && rm -rf /var/lib/apt/lists/*

example of some commands for removing package manager cache:

Be specific about COPY

Instead of copying the whole thing into the container,

COPY .  /app

only copy what is required

COPY package.json server.js /app

USER

If we don’t specify any USER in Dockerfiles, it defaults to root user which can potentially access docker host system. Using container as non-root is one of the widespread best practice for security.

If service can run without privileges, use USER to change to a non-root user, you can create a user in Dockerfile or standard maintained project such as apache or node provides user readily.

RUN chown -R node:node /app
USER node
CMD ["npm", "start", "--prefix", "app"]

Multistage build

COPY instead of ADD

COPY /source/file/path  /destination/path

Don’t Keep Secrets

Linting

$ hadolint Dockerfile

Dockerfile:1 DL3006 Always tag the version of an image explicitly
Dockerfile:3 DL3009 Delete the apt-get lists after installing something
Dockerfile:4 DL3008 Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
Dockerfile:4 DL3015 Avoid additional packages by specifying `--no-install-recommends`
Dockerfile:5 DL3008 Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
Dockerfile:5 DL3015 Avoid additional packages by specifying `--no-install-recommends`
Dockerfile:6 DL4006 Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check

Building

When you build the image, also consider what tags you are providing.

# :latest doesn't care
$ docker build -t company/image_name:0.1 .
# :latest was created
$ docker build -t company/image_name
# :latest doesn't care
$ docker build -t company/image_name:0.2 .
# :latest was updated
$ docker build -t company/image_name:latest .