DEV Community

Mohammad Faisal
Mohammad Faisal

Posted on

How I Reduced Docker Image Size from 1.43 GB to 22.4 MB

To read more articles like this, visit my blog

If you are working in web development, then you probably already know about the idea of containerization and how it’s so great and everything.

But when working with Docker image size is a great concern. It’s often over 1.43 GB for just the boilerplate project that we get from create-react-app

Today we will containerize a ReactJS application and learn some tricks about how we can reduce the image size and, at the same time, improve performance.

The tricks will be shown for ReactJS, but it applies to any NodeJS application.

Step 1. Create Your Project

  • Simply go to your terminal and type
npx create-react-app docker-image-test
Enter fullscreen mode Exit fullscreen mode
  • Then create-react-app will provide you with your basic React application

  • Afterward, go into the root directory and run the project.

cd docker-image-test
yarn install
yarn start
Enter fullscreen mode Exit fullscreen mode

Step 2. Build Your First Image

  • Inside the root directory of your project, create a file named Dockerfile and paste the following code there.
FROM node:12

WORKDIR /app

COPY package.json ./

RUN yarn install

COPY . .

EXPOSE 3000

CMD ["yarn", "start"]
Enter fullscreen mode Exit fullscreen mode
  • Notice that we are getting our base image node:12 from the docker hub, installing our dependencies, and running the basic commands. (We won’t go into the details of docker commands here)

  • Now from your terminal, build the image for your container.

docker build -t docker-image-test .
Enter fullscreen mode Exit fullscreen mode
  • Docker will build your image. After finishing, you can see your image using this command.
docker images
Enter fullscreen mode Exit fullscreen mode

On top of the list is our newly created image, and on the far right side, we can see the size of the image. It’s 1.43GB for now.

docker images on our machine

  • We can run the image by using the following command
docker run --rm -it -p 3000:3000/tcp docker-image-test:latest
Enter fullscreen mode Exit fullscreen mode

You can go to your browser and refresh the page to verify that it’s still working.

Step 3. Change the Base Image

  • We used node:12 as our base image in the previous configuration. But traditionally, node images are based on Ubuntu which is unnecessarily heavy for our simple React application.

  • From the DockerHub (Official docker image registry), we can see that Alpine-based images are much smaller than Ubuntu-based images, and they are packaged with just the minimum dependency.

  • A comparison between the size of these base images is shown below.

node:12 vs node:12-alpine

  • Now we will use node:12-alpine as our base image and see what happens.
FROM node:12-alpine

WORKDIR /app

COPY package.json ./

RUN yarn install

COPY . .

EXPOSE 3000

CMD ["yarn", "start"]
Enter fullscreen mode Exit fullscreen mode
  • Then we build our image and see the size as we did before.

image with node-alpine

Wow! Our image size is reduced to 580MB only. That’s a great improvement. But can we do better?

Step 4. Multistage Build

  • In our previous configurations, we were copying all of our source codes into the working directory.

  • But it’s unnecessary as we only need the build folder to serve our website. So now, we will use the concept of multi-stage build to cut down the unnecessary code and dependencies from our final image.

  • The configuration will look something like this.

# STAGE 1

FROM node:12-alpine AS build

WORKDIR /app

COPY package.json ./

RUN yarn  install

COPY . /app

RUN yarn build


# STAGE 2

FROM node:12-alpine

WORKDIR /app

RUN npm install -g webserver.local

COPY --from=build /app/build ./build

EXPOSE 3000

CMD webserver.local -d ./build
Enter fullscreen mode Exit fullscreen mode
  • In the first stage, we install the dependencies and build our project

  • In the second stage, we copy the contents of the build folder from the previous stage and use that to serve our application.

  • This way, we don’t have unnecessary dependencies and codes inside our final image.

Next, we build our image and see the image from the list as before

Image with multi-stage build

Now our image size is 97.5MB only. How great is that?

Step 5. Using NGINX

  • We are using a node server to serve the static assets of our ReactJS application, which is not the best option for serving static content.

  • We can use a more efficient and lightweight server like **Nginx** to serve our application and see if it improves our performance and reduce the size.

  • Our final Docker configuration file will look something like this.

# STAGE 1

FROM node:12-alpine AS build

WORKDIR /app

COPY package.json ./

RUN yarn  install

COPY . /app

RUN yarn build

# STAGE 2

FROM nginx:stable-alpine

COPY --from=build /app/build /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
Enter fullscreen mode Exit fullscreen mode
  • We are changing the second stage of our docker configuration to serve our application using Nginx.

  • Then we build our image using this current configuration.

Final Docker Image Size

  • The image size is reduced to 22.4MB only!

  • And at the same time, we are using a more performant server to serve our awesome application.

  • We can verify if our application is still working using the following command.

docker run --rm  -it -p 3000:80/tcp docker-image-test:latest
Enter fullscreen mode Exit fullscreen mode
  • Notice we are exposing the container's 80 port to the outside as Nginx, by default, will be available on port 80 inside the container.

So these are some easy tricks you can apply to any of your NodeJS projects to reduce the image size by a huge margin. Now your container is truly more portable and efficient.

That’s it for today. Happy Coding!

Have something to say?

Get in touch with me via LinkedIn

Top comments (13)

Collapse
 
rahuls profile image
Rahul

Hello Faisal,

Really insightful blog post. I just got a chance to work with docker and some parts are easy and other parts are bit difficult for me.

I have few questions here:
1) After building the second image in the multi stage containerization, will the first image get discarded automatically?
2) Why is that the node server is not good for serving static content? Why is Nginx preferred? Is it because Nginx is good for production.

Thank you.

Collapse
 
mohammadfaisal profile image
Mohammad Faisal
  1. yes
  2. NodeJS is not optimized to serve static assets. nginx is specificly designed for acting as a high performance web server. That's why it's good
Collapse
 
viniciuslagogehrke profile image
Vinicius Lago Gehrke

Interesting and helpfull article. But there's a trick here that's more React then Docker: you move from a development build on step3 wich is built focused on Dev Experience (with Hot Reload and more), to a production build on step4 that serves the static files and minified JS that is way smaller.

The thing is they serve different proposals:
The first dockerfile can be used so every developer can run it on their machines with the same environment without installing anything apart from Docker

The second dockerfile is focused on serving the final content to production

Collapse
 
timhub profile image
Tech Tim (@TechTim42) • Edited

good practice.

I have never thought about to wrap nginx in an app level container, but it does offer a good option to optimise web app docker size

Collapse
 
mohammadfaisal profile image
Mohammad Faisal

Right. It's probably better to use s3 or something to host a simple static site.

But if you go down the docker path, nginx is the best choice.

Collapse
 
jon_snow789 profile image
Jon Snow

Great Article 🧡👍

Collapse
 
getcodescandy profile image
codescandy

Awesome thank you.

Collapse
 
quocbao64 profile image
quocbao64

Nice post, thank you for sharing

Collapse
 
cuongquang profile image
cuongquang

Nice post! I never compare docker container size this way before ❤️

Collapse
 
msnisha profile image
Nish

I was thinking that is what everyone was doing using nginx to serve ReactJS application usign nodejs is definitely an overkill and I never tried using it for statics websites. Nginx containers are so light you can run many containers in on raspberry pi you could host your websites from home when you are starting with new applications.

Collapse
 
mohammadfaisal profile image
Mohammad Faisal

Well.... now you know :P

Collapse
 
neelbrahmakshatriya profile image
Neel Brahmakshatriya

Interesting article!

I was wondering that can the size of the docker image with yarn start be reduced without making a build?

Collapse
 
madeindra profile image
Made Indra • Edited

Nice post! I hope you could try this idea for your next blog post.

Use Node v20 to build a single executable of your app and then use scratch for the base image in the second stage