{"id":4009,"date":"2022-01-07T13:58:00","date_gmt":"2022-01-07T12:58:00","guid":{"rendered":"https:\/\/blog.besharp.it\/?p=4009"},"modified":"2022-01-05T16:12:39","modified_gmt":"2022-01-05T15:12:39","slug":"deep-dive-in-docker-tips-and-tricks-to-build-docker-images-optimized-for-security-and-size","status":"publish","type":"post","link":"https:\/\/blog.besharp.it\/deep-dive-in-docker-tips-and-tricks-to-build-docker-images-optimized-for-security-and-size\/","title":{"rendered":"Deep dive in Docker: Tips and Tricks to build Docker images optimized for security and size."},"content":{"rendered":"\n
Nowadays we have a lot of workloads that are built on top of Docker and it is crucial to keep our Docker images secure and optimized for the cloud. We already covered Docker in our blog, you can dig further on the origins of Docker<\/a> or see a use case with WordPress<\/a>.<\/p>\n\n\n\n In the next paragraphs, we will see a few tips on how we can avoid the most common security vulnerabilities and how to optimize the size of our Docker images.<\/p>\n\n\n\n But first, we need to understand one of the core concepts of Docker: the layering system.<\/p>\n\n\n\n The Dockerfile is the main file that defines how a Docker image is built. It defines the base image from which we start, the application\u2019s dependencies, how the application is packaged and how it is run.<\/p>\n\n\n\n When we start the build process from a Dockerfile, the Docker engine creates a series of layers, one for each command in the Dockerfile. Every time we execute a command in the Dockerfile, a new layer containing the changes is created on top of the previous one. The Docker engine executes the commands sequentially and eventually combines all the layers to create the final image.<\/p>\n\n\n\n Keeping the size small generally means that it is faster to build, deploy and run. To achieve that, a good idea is to start from a base image that is itself very small. You can use `alpine:3.15` which is just about 5.6 MB instead of `ubuntu:20.04` which is (at the time of this article) 72 MB.<\/p>\n\n\n\n Alpine<\/a> is a general-purpose Linux distribution built around musl libc and <\/a>BusyBox that aims for simplicity, security, and resource efficiency. It contains only absolutely necessary packages because you know\u2026 if it\u2019s not included it can\u2019t break<\/em>.<\/p>\n\n\n\n Keep in mind that it also has some drawbacks. For example, since Alpine is based on the musl C library, instead of the most widely used GNU C Library, you may encounter problems with some C-based dependencies.<\/p>\n\n\n\n Aside from using Alpine as a base image, another method for reducing the size of your images is using multistage builds. A multistage build consists of a single Dockerfile in which we define multiple FROM instructions and each one defines a new stage.<\/p>\n\n\n\n In each FROM we can start from a previous stage in order to take advantage of cached build artifacts or we can start from an entirely new base image and just copy some artifacts from the previous stage.<\/p>\n\n\n\n Let\u2019s see a quick example:<\/p>\n\n\n\n In this multi-stage Dockerfile we created a base image that has all the required dependencies, we then created a builder stage, and finally, for the actual runtime image, we started from a clean image, installed the production requirements, and selectively copied only the required artifacts.<\/p>\n\n\n\n By default, all stages are built but we can choose to build a specific stage with the flag `–target <stage-name>`<\/p>\n\n\n\n `docker build –target release –tag image .`<\/p>\n\n\n\nHow can I keep my Docker image small?<\/h2>\n\n\n\n
Start from a small base image. Alpine is a good choice<\/strong><\/h3>\n\n\n\n
Use multi-stage Dockerfiles<\/strong><\/h3>\n\n\n\n
# Base image with dependencies\nFROM node:17.3.0-alpine3.12 AS base\nWORKDIR \/app\n# Copy package.json and package-lock.json\nCOPY package*.json .\/\n# Install dependencies\nRUN npm install\n \n \n# Build Stage\nFROM base AS build\nWORKDIR \/app\nCOPY . .\/\n# Build and bundle static files\nRUN npm run build\n \n \n# Release Stage\nFROM node:17.3.0-alpine3.12 AS release\nWORKDIR \/app\nCOPY --from=base \/app\/package.json .\/\n# Install app dependencies\nRUN npm install --only=production\nCOPY --from=build \/app\/dist\/ .\/\nCMD [ \"npm\", \"run\", \"start\" ]\n<\/code><\/pre>\n\n\n\n