
Author | The Agile Crafter
Planning | Tian Xiaoxu
Docker is a platform for software developers and system administrators to build, run, and share applications with containers. A container is a process running in an isolated environment, operating on its own file system, which is built using a Docker image. The image contains everything needed to run the application (compiled code, dependencies, libraries, etc.). The image is defined using a Dockerfile.
The term dockerization or containerization is often used to define the process of creating Docker containers.
Containers are popular due to the following advantages:
-
Flexibility: Even the most complex applications can be containerized.
-
Lightweight: Containers share the host kernel, making them much more efficient than virtual machines.
-
Portability: Built locally, run anywhere.
-
Loose Coupling: Containers are self-contained; replacing or upgrading one container does not interrupt others.
-
Security: Containers enforce strict limits and isolation on processes without requiring user configuration.
In this article, I will focus on how to optimize Docker images to make them lightweight.
Let’s start with an example where we build a React application and containerize it. After running the npx command and creating the Dockerfile, we end up with the file structure shown in Figure 1.
npx create-react-app app --template typescript
Figure 1: File Structure
If we build a basic Dockerfile (as shown below), we end up with a 1.16 GB image:
FROM node:10
WORKDIR /app
COPY app /app
RUN npm install -g webserver.local
RUN npm install && npm run build
EXPOSE 3000
CMD webserver.local -d ./build
Figure 2: Initial Size of the Image is 1.16GB
Step One Optimization: Use Lightweight Base Images
There are several images available for download on Docker Hub (the public Docker repository), each with different characteristics and sizes.
Generally, images based on Alpine or BusyBox are much smaller compared to those based on other Linux distributions (like Ubuntu). This is because Alpine images and similar others are optimized to include only the minimal necessary packages. In the image below, you can see the size comparison between Ubuntu, Alpine, Node, and Node based on Alpine images.
Figure 3: Different Sizes of Base Images
By modifying the Dockerfile and using Alpine as the base image, our final image size is 330MB:
FROM node:10-alpine
WORKDIR /app
COPY app /app
RUN npm install -g webserver.local
RUN npm install && npm run build
EXPOSE 3000
CMD webserver.local -d ./build
Figure 4: After the First Step Optimization, the Image Size is 330MB
Step Two Optimization: Multi-Stage Builds
With multi-stage builds, we can use multiple base images in the Dockerfile and copy compiled products, configuration files, etc., from one stage to another, allowing us to discard unnecessary items.
In this case, what we need to deploy the React application is the compiled code; we do not need the source files, nor the node_modules directory and package.json file, etc.
By modifying the Dockerfile to the following content, we end up with an image size of 91.5MB. Remember, the image from the first stage (lines 1-4) will not be automatically deleted; Docker keeps it in cache, allowing faster builds if the same stages are executed in another build process. So you must manually delete the first stage image.
FROM node:10-alpine AS build
WORKDIR /app
COPY app /app
RUN npm install && npm run build
FROM node:10-alpine
WORKDIR /app
RUN npm install -g webserver.local
COPY --from=build /app/build ./build
EXPOSE 3000
CMD webserver.local -d ./build
Figure 5: After the Second Step Optimization, the Image Size is 91.5MB
Now we have a Dockerfile with two stages: in the first stage, we compile the project, and in the second stage, we deploy the application on a web server. However, the Node container is not the best choice for serving web pages (HTML, CSS, and JavaScript files, images, etc.); the best choice is to use a service like Nginx or Apache. In this example, I will use Nginx.
By modifying the Dockerfile to the following content, our final image size is 22.4MB. If we run this container, we can see that the web page works fine without any issues (Figure 7).
FROM node:10-alpine AS build
WORKDIR /app
COPY app /app
RUN npm install && npm run build
FROM nginx:stable-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
Figure 6: After the Third Step Optimization, the Image Size is 22.4MB
Figure 7: Final Container Running Result
InfoQ Reader Group is now online! You can scan the QR code below, add the InfoQ assistant, and reply with the keyword “Join Group” to apply for joining. Reply “Materials” to get the materials package portal. After registering on the InfoQ website, you can receive any one Geek Time course for free! You can chat freely with InfoQ readers, get in touch with editors, and receive valuable technical gifts and participate in exciting activities, come join us!
-
Click to see less bugs 👇