Dockerfile best practices

In this blog, we are going to talk about the best practices to be followed when writing the Dockerfile on your own. It is recommended to make the resulting docker image optimal and the minimum in size. Here, I came up with a list of best practices but limited to the below.

  1. Base Image: Always try to use the latest/most stable images for your applications and also make ensure that they are production ready. Read through the release notes or updates for the docker images version. It is recommended to use the docker images from trusted vendors that too with active contributors.
  2. Use Labels: The labels always help to identify the docker images in the repositories based on category. You need to add the labels in such a way that describes your docker such as name, version, etc as in the below example.
    # Set one or more individual labels
    LABEL vendor="CloudTechiee"
    LABEL release-date="2023-Apr-25"
    LABEL is-production="Yes"
  1. Environment Variables: Use of Environment variables while building an image. These will help you to configure and deploy containers with different settings in cases such as running images for different environments, configurations, etc.
# Dockerfile
FROM alpine:latest

ENV NAME=$NAME_INPUT
echo $NAME

RUN echo "My Name is: $NAME"

CMD ["/sample.sh"]

=========================================
# Build the image
docker build -t demo_image:v1 .

# Run the docker image

# This prints the message: My Name is: Demo Name 1
docker run --name demo_con -d demo_image:v1 -e NAME_INPUT='Demo Name 1'

# This prints the message: My Name is: Demo Name 2
docker run --name demo_con -d demo_image:v1 -e NAME_INPUT='Demo Name 2'
  1. Avoid Unnecessary Packages: Most people try to install many things to sort out one or other error/issue. The more you install the packages, the size of the docker image keeps on increasing. So, better to avoid installing unnecessary packages.
  2. Simplify and Minimize the RUN statements: Adding RUN time each time, adds a layer for your docker image, the also eventually increases the size of the docker image. Instead of adding multiple RUN statements, try to consolidate them into one RUN statement.
# Dockerfile with multiple RUNs
FROM ubuntu:latest

RUN apt-get install xyz
RUN apt-get install abc

CMD ["/sample.sh"]
# Dockerfile with consolidated RUNs
FROM ubuntu:latest

RUN apt-get install xyz && \
         apt-get install abc

CMD ["/sample.sh"]
  1. User Build Cache: When creating the images, docker creates the cache of layers. Use of a cache help you to speed up the build process. If you do want to use build cache, you can add an explicit argument during the build with the “–no-cache” option.
  2. Expose required ports: Only expose the ports that your application configured to listen. Do not open unnecessary ports which are not required.
  3. ENTRYPOINT and CMD: ENTRYPOINT and CMD statements help you configure the initial command or process to run when the container gets created. However, the instruction given for CMD can be overwritten, but with ENTRYPOINT you can not overwrite the inputs. CMD: Sets default parameters that can be overridden from the Docker command line interface (CLI) while running a Docker container. ENTRYPOINT: Sets default parameters that cannot be overridden while executing Docker containers with CLI parameters. However, the instruction given gets appended with default parameters.
FROM ubuntu
CMD ["echo", "Hello World"]

docker build -t cmd-demo .

# This prints the below message
docker run cmd-demo
Hello World

# This prints the below message
docker run cmd-demo CloudTechiee
CloudTechiee
FROM ubuntu
ENTRYPOINT ["echo", "Hello World"]

docker build -t cmd-demo .

# This prints the below message
docker run cmd-demo
Hello World

# This prints the below message
docker run cmd-demo CloudTechiee
Hellow World CloudTechiee
  1. Multi-Stage Builds: Multi-Stage build helps you build a slim version of your production-ready docker images. To build an efficient docker image, you need to avoid unnecessary layers and installations. In earlier days, all the installations, application build, and packaging are done in one docker file which results in docker images with bigger sizes. Multi-Stage allows you to have a stage for docker image for building and packaging your app and another stage is just building an image with the application package. Below is one example of building a multi-stage docker image. The image is used just for build and package and the second one is the final slimmed version of the image with the package added.
FROM node:12.13.0-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx
EXPOSE 80
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/build /usr/share/nginx/html
  1. Security Scan: Make ensure that your image goes through security testing before deploying to production. Tools are available in the market for scanning and for security checks of your image.
  2. Run as a non-root user: Docker containers by default run with root privileges and the application too has the same. This is another major security. concern as the hackers may gain root access. So, it is better to run the container with specified use with required privileges. Below is an example.
FROM Ubuntu:latest
# Add a new user "john" with user id 8877
RUN useradd -u 1234 demo_user
# Change to non-root privilege
USER demo_user

# Build image
docker build -t demo_user .

# Docker container at run time
docker run --rm demo_user id

# This prints
uid=1234(demo_user) gid=1234(demo_user) groups=1234(demo_user)
  1. Use of dockerignore file: Sometimes, you really do not want to copy all files to docker images, in such case create a .dockerigore file in the same place as your dockerfile and mention the file/directory names in the ignore file. Let’s consider that you have a directory as below. Now, you really do not need the git, logs folder to be there in your final docker image. So, in such cases, write a .dockerignore file to exclude these directories from getting copied to the docker image. When you build the docker image, docker will consider skipping the below-listed paths in dockerigonore file.

These are some of the best practices you need to follow when building the docker images. Hope the blog helps you in some way.

Share your love