4th gen AMD EPYC coming soon Pre-order now

Docker Multi-stage Build: How to Make Your Docker Image Smaller

October 12th, 2023
Docker Multi-stage Build: How to Make Your Docker Image Smaller

Docker multi-stage build helps to reduce the size of your Docker image. Docker containers are meant to be lightweight solutions. However, if you are not careful, you could end up with a Docker image that is way larger than necessary or than it should be, as every instruction included in the Dockerfile adds a layer to the image. Large Docker images consume more storage space and can slow down your development cycle.

Docker multi-stage build is one way to solve this problem. In this article, we'll look at what Docker multi-stage build is and demonstrate how to use it to make your Docker images smaller with an example.

Prerequisites

This article includes a hands-on demonstration, to follow along, ensure you have: Docker or Docker Desktop installed. We are going to use a simple React application - a counter app - in this guide.

What is a Docker image?

Built from instructions defined in Dockerfiles, a Docker image is a portable package containing everything needed to run an application optimized to deliver software quickly and consistently.

What is Docker multi-stage build?

Docker multi-stage build is a feature that allows you to have more than one FROM statement, each representing a stage in your Dockerfile. Each stage starts with a fresh image that can be used to perform specific tasks. With multi-stage Dockerfiles, you can also share data between build stages. This way, you can build the application in one stage and copy only the necessary components that the application needs to run to the final image, resulting in smaller and more optimized Docker images.

Each stage in the Dockerfile will generate its container image, but at the end of the build, Docker will commit only one of these images into the local container registry. By default, this will be the image produced by the last stage in the Docker file. If you want the image from a different stage, you can indicate using the target=<stage name> with the docker build command.

Ready to supercharge your Docker infrastructure? Scale effortlessly and enjoy flexible storage with Cherry Servers bare metal or virtual servers. Eliminate infrastructure headaches with free 24/7 technical support, pay-as-you-go pricing, and global availability.

When to use Docker multi-stage build?

Multi-stage builds are great when you need to create an artifact or binary. Building such requires a lot of dependencies. However, once the binary is built, you don't need the dependencies to run it.

You should consider using Docker multi-stage builds when your application has a complex build process and several dependencies or when you want to separate the build and runtime environments.

Why use Docker multi-stage build?

Here are some benefits of using Docker multi-stage build:

  • Generated images are smaller. The final image is typically much smaller than the one produced by a normal build, as the resulting image includes just what the application needs to run.
  • More secure containers. Packages and dependencies need to be kept up to date because they can be a potential source of vulnerability for attackers to exploit. Therefore, you should only keep the required dependencies. Using Docker multi-stage build means the resulting container will be more secure because your final image includes only what it needs to run the application.
  • Faster deployment. Smaller images mean less time to transfer or quicker CI/CD builds, faster deployment time, and improved performance.

How to use Docker multi-stage build: 6 Steps

Let's use a simple React application to demonstrate how a Docker multi-stage build can be used to reduce a Docker image size. You would compare the final Docker image size for a single-stage build and for a multi-stage build.

Step 1 - Create a Dockerfile

First, create a Dockerfile in the application's root directory and add the following content to it.

# Use node:16-alpine image as a parent image
FROM node:16-alpine

# Create app directory
WORKDIR /usr/src/app

# Copy package.json files to the working directory
COPY package*.json ./

# Install app dependencies
RUN npm install

# Copy the source files
COPY . .

# Build the React app for production
RUN npm run build

# Expose port 3000 for serving the app
EXPOSE 3000

# Command to run the app
CMD ["npm", "start"]

Step 2 - Build a Docker image

Next, build the Docker image using the docker build command.

docker build -t single-stage-build .

build the docker image with the single-stage Dockerfile

This is a normal or single-stage Dockerfile. When you run the docker build command, it will launch a build process starting with the base image. The resulting image will contain the build tools and the source code, the runtime environment that includes the dependencies to run your application, and libraries, but not all of these are needed in the final image for the application to run.

Step 3 - Check the Docker image size

Now, check the size of the final image using the docker images command to list the Docker images on your Docker environment.

docker images

You should now see the image you just built, and in the size column, you should see the size of the image. In this case, the resulting image is 430MB. That is a lot for a simple application with just a few lines of code.

List docker images

You can confirm that you can access the application by running the docker container using the docker run command.

docker run -d -p 3000:3000 single-stage-build

run docker image

This will create a docker container from the "single-stage build" image, mapping port 3000 on the host computer to port 3000 in the container. Going to localhost:3000 on your browser, you should be able to view the application.

view the application

Step 4 - Restructure the Dockerfile for multi-stage builds

For a small application used here, you don't need a Docker image of 420MB. So, how can you ensure you're building optimal Docker images containing just what your application needs to run?

As the next step, restructure the Dockerfile to have multiple stages by replacing the content of the existing Dockerfile with the code provided below:

# First stage - Building the application
# Use node:16-a;pine image as a parent image
FROM node:16-alpine AS build

# Create app directory
WORKDIR /usr/src/app

# Copy package.json files to the working directory
COPY package*.json ./

# Install app dependencies
RUN npm install

# Copy the source files
COPY . .

# Build the React app for production
RUN npm run build

# Second stage - Serve the application
FROM nginx:alpine

# Copy build files to Nginx
COPY --from=build /usr/src/app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

The Dockerfile now has two stages or steps (characterized by the FROM statements). This will create a two-stage build process: the first stage builds the React application, and the second stage will serve the built application using Nginx. The COPY command in the second stage copies the contents of the build directory from the first stage (alias "build") into the Nginx web server default directory. This sets up Nginx to serve your React application.

Step 5 - Build a Docker image with multi-stage builds

Now run the docker build command with the updated Dockerfile.

docker build -t multi-stage-build .

build the docker image using a multi-stage Dockerfile

Step 6 - Check the image size

List your docker images once again and see if the multi-stage build made any difference.

docker images

run the docker images command

You can see that the final image has been reduced to just 43.2MB. That's a lot less than the initial image. Now confirm that the application still gets displayed or works as it should be spinning up a container with the new image.

docker run -d -p 80:80 multi-stage-build

run the new docker container

This will create a docker container from the "multi-stage build" image, mapping port 80 on the host computer to port 80 in the container. Going to localhost:80 on your browser, you should be able to access the application just as before.

access the application on your browser

Explore how web hosting service provider Debesis improved its service quality, performance, and reliability by migrating to Cherry Servers' bare-metal servers. "Cherry Servers engineers always help when we need them, while their customer service quality is a blast!"

Docker multi-stage builds best practices

Here are some best practices to keep in mind when using Docker multi-stage build:

  • Avoid including unnecessary dependencies and files.
  • Use an appropriate base or parent image, one that only contains the packages you need.
  • Avoid including the application data in the container, as this can increase the size of your image. You can use Docker volumes for the application data instead.

Conclusion

In this article, you learned about Docker multi-stage build, how it compares to normal Docker builds and how it is a great feature for creating smaller and more efficient images. You can take advantage of this feature to create optimized docker images for production deployment.

Thanks for learning with Cherry Servers!

Try Cherry Servers' open cloud infrastructure for Docker. Cloud Storage VPS and Cloud VDS offer optimized resources for multi-phase builds, 99.97% uptime, customizable CPU/RAM, hourly billing, three-minute deployment, and 24/7 technical support.

Learn more about us here.

Goodness is an expert in technical support, JavaScript programming, and cloud/DevOps engineering. She acquired her skills from studies in computer science and hands-on working experience. Over the years, Goodness has also honed the skills of creating, updating, and improving software documentation, writing instruction guides/manuals and technical articles for the knowledge base, and developing website content. Goodness is an expert in technical writing, DevOps engineering, Linux, Docker, containers, open-source, frontend development, and JavaScript. She also contributes to the documentation of open-source projects like Ansible and ODK-X. Goodness received her B.Sc. in Computer Science from the University of Port Harcourt and resides in Port Harcourt, Nigeria.

Cloud VPS - Cheaper Each Month

Start with $9.99 and pay $0.5 less until your price reaches $6 / month.

We use cookies to ensure seamless user experience for our website. Required cookies - technical, functional and analytical - are set automatically. Please accept the use of targeted cookies to ensure the best marketing experience for your user journey. You may revoke your consent at any time through our Cookie Policy.
build: e4941077.621