Writing a Dockerfile. Best practics

We publish a new translation and hope that the author’s recommendations will help you optimize your Docker image.

Since its inception, Docker has revolutionized the way we use containers. This is mainly due to the simplicity that Docker provides. It allows anyone to use it without having to understand any complex container-related topics.

If you are new to Docker, you can choose a template (base image) and define your instructions (Docker file commands) to put your code inside the image and run it.

While Docker’s simplicity helps get the job done, optimizing it comes with experience and usually takes time.

Since I have been working with Docker for a long time, I decided to share with you how to create containers better from day one.

1. Define cached units ➱

Did you know that each team RUNincluded in Dockerfile affects caching level?

Using multiple RUN commands to install packages will affect the performance and efficiency of the build process. Using a single RUN command to install all packages and dependencies will help you create one cached unit instead of several.

RUN apt-get update && apt-get install -y 
    aufs-tools 
    automake 
    build-essential 
    curl 
    dpkg-sig 
    libcap-dev 
    libsqlite3-dev 
    mercurial 
    reprepro 
    ruby1.9.1 
    ruby1.9.1-dev 
    s3cmd=1.1.*

2. Reduce the image size

Image size plays an important role in making a good Dockerfile. Using smaller images will result in faster deployment and less room for attacks.

Remove unnecessary dependencies

Avoid installing unnecessary tools such as debugging tools into the image.

If the package manager uses the recommended packages to install automatically, use the package manager flags and avoid installing unnecessary dependencies.

RUN apt-get update && apt-get -y install --no-install-recommends

Tip: Share Reusable Components Between Projects with Bit (Github).

Bit makes it easy to document, share, and reuse independent components between projects. Use it to reuse code, maintain design consistency, team collaboration, increase delivery speed, and build scalable applications.

Bit supports Node, TypeScript, React, Vue, Angular, etc.


Explore the components published on Bit.dev

3. Image support

Choosing the right base image for your application is very important.

Use the official Docker image

Using the official Docker image reduces the burden on unnecessary dependencies that make the image larger. There are 3 main benefits to using the official image:

  • this allows us to use an image based on best practices,
  • reliability of the image and its safety,
  • higher trust and safety.

# загрузить официальный базовый образ
FROM node:13.12.0-alpine

# установить рабочий каталог
WORKDIR /app

# добавить `/app/node_modules/.bin` в $PATH
ENV PATH /app/node_modules/.bin:$PATH

Use specific tags

It is recommended to use a specific tag when choosing a base image. Do not use for image latest tag. Latest the tag can undergo breaking changes over time.

# загрузить официальный базовый образ
FROM node:13.12.0-alpine

Use minimal flavors

Minimal Flavors reduce the image size. This helps you deploy applications faster and more securely.

As you can see from the above image, when using minimal flavors, the image takes up less space. Most of the skins use alpine flavor. Alpine is a very lightweight image with a standard 2MB size.

By using an alpine based image, we can significantly reduce the size of the image.

4. Reproducibility

Building from source in a consistent environment

When building an app with Docker, it’s best to build the app in a managed environment to ensure consistency.

We should avoid creating applications locally and adding them to the registry.

Otherwise, packages that you may have installed in your local environment may affect the consistency of the image. I hope you don’t want to find yourself in this situation, as it compromises one of the main benefits of Docker – consistent execution across environments.

Using multi-stage assemblies to remove dependencies

It is recommended that you use a multi-stage application deployment method.

This eliminates the use of assembly dependencies in a running container.

We can build an application using a unique build image that has developer dependencies and move the compiled binaries into a separate container image to run it.

# Stage 0, "build-stage", based on Node.js, to build and compile the frontend
FROM node:13.12.0 as build-stage
WORKDIR /app
COPY package*.json /app/
RUN npm install
COPY ./ /app/
RUN npm run build
# Stage 1, based on Nginx, to have only the compiled app, ready for production with Nginx
FROM nginx:1.15
COPY --from=build-stage /app/build/ /usr/share/nginx/html

There are two separate steps in the above Dockerfile. Stage 0 is used to build a node application from the original node image, and stage 1 is used to copy binaries from the build image to the web server image (Nginx) that ultimately serves the application.

Conclusion

That’s all I wanted to tell you. If you’re new to Docker, I recommend trying these practices when building your first image. This will help you understand the topic more deeply and allow you to use Docker effectively from the beginning.

If you know of other cool practices, share in the comments. Thanks for reading!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *