Docker Image Optimization
Optimizing a Docker image involves several best practices aimed at reducing the image size, improving build times, and ensuring the image runs efficiently in production. Here are a few best practices that can help to shrink docker image size.
Minimal Base Image
Base image plays an important role for your container, It is more like a blueprint of the file system and dependencies or a specific set of directories that are required to run your application with basic configuration in a container environment.
It has been observed that a base image is the main part that shrinks the image size, therefore, it is recommended to use a lightweight base image like
alpine
which is only about 5 MB in size.Let us understand this with an example.
Dockerfile with large size
FROM node:latest WORKDIR /usr/src/app COPY . . RUN npm install EXPOSE 3000 CMD ["node", "app.js"]
The size of the image is 1.14 GB as shown below when you build an image from the above Dockerfile.
REPOSITORY TAG IMAGE ID CREATED SIZE my_app latest 00d8fe01d46e 5 minutes ago 1.14GB
Dockerfile with shrink size
Let us optimize the above docker file using a minimal base image (alpine).
FROM alpine:latest WORKDIR /usr/src/app COPY . . RUN apk add --no-cache nodejs npm RUN npm install EXPOSE 3000 CMD ["node", "app.js"]
The size of the image shrinks 1.14 GB to 103 MB as shown below when you build an image from the above Dockerfile.
REPOSITORY TAG IMAGE ID CREATED SIZE my_app_shrink latest 39ed45d97204 20 seconds ago 103MB
Multi-Stage Build
Multi-stage builds are a powerful feature in Docker that allows you to use multiple FROM statements in your Dockerfile. This enables you to separate the build environment from the runtime environment, resulting in smaller and more secure images.
Let us change our original dockerfile into a multi-stage build
# Stage 1 FROM node:latest as build WORKDIR /app COPY . . RUN npm install # Build the application RUN npm run build # Stage 2: Production Stage FROM node:16-alpine WORKDIR /app COPY --from=build /app/build ./build COPY --from=build /app/package*.json ./ RUN npm install --production EXPOSE 80 CMD ["node", "build/app.js"]
The above code snippet is divided into two stages, Stage 1 is to build, compress the application, and put it into the build folder inside the app. Stage 2 will copy the build folders and other dependencies in the production image.
Minimize Layers
Docker image is composed of a series of layers on top of each other. Each dockerfile command (FROM, RUN, COPY, etc.) adds a new layer to the image. unchanged layers are cached by docker to boost the image build process and efficient use of resources. Each layer increases image size, therefore, it is quite obvious to minimize layers as much as you can, Here are a few tips to minimize layers.
Unchanged layers should be placed on top of the image, for example installing system packages or dependencies.
Combine RUN instruction as much as possible.
- Frequently changes should be placed on the bottom of the image.
For Example: Let us re-write the above docker file by minimizing layers.
FROM alpine:latest WORKDIR /usr/src/app COPY . . RUN apk add --no-cache nodejs npm && npm install EXPOSE 3000 CMD ["node", "app.js"]
Optimize Cache Usage
Unchanged layers are cached by the docker when you are building images so orders matter, Arrange commands that change less frequently at the top of the Dockerfile to take advantage of Docker's layer caching mechanism.
Remove Unnecessary Files
Delete unnecessary files and package caches to reduce the image size.
FROM alpine:latest WORKDIR /usr/src/app COPY . . RUN apk add --no-cache nodejs npm && npm install RUN rm -rf /var/cache/apk/* /tmp/* /var/tmp/* EXPOSE 3000 CMD ["node", "app.js"]
Include .dockerignore File
Create a .dockerignore file to exclude files and directories that are not needed in the Docker image.
.git node_modules tmp