If you want to reduce docker image size, you need to use the standard best practices in building a Docker Image.
This blog talks about different optimization techniques that you can quickly implement to make the smallest and minimal docker image. We will also look at some of the best tools for Docker Image Optimization.
Dockeras a container engine makes it easy to take a piece of code & run it inside a container. It enables engineers to collect all the code dependencies and files into a single location which can be run anywhere, quite quickly & easily.
The whole concept of “run anywhere” images starts from a simple configuration file called Dockerfile. First, we add all the build instructions, such as the code dependencies, commands, and base image details, in Dockerfile.
Need for Docker Image Optimization
Even though the Docker build process is easy, many organizations make the mistake of buildingbloated Docker imageswithout optimizing the container images.
In typical software development, each service will have multiple versions/releases, and each version requires more dependencies, commands, and configs. This introduces a challenge in Docker image build, as now – the same code requires more time & resources to be built before it can be shipped as a container.
I have seen cases where the initial application image started with 350MB, and over time it grew to more than 1.5 GB.
Also, by installing unwanted libraries, we increase the chance of a potential security risk by increasing the attack surface.
Therefore, DevOps engineers must optimize the docker images to ensure that the docker image is not getting bloated after application builds or future releases. Not just for production environments, at every stage in the CI/CD process, you should optimize your docker images.
Also, with container orchestration tools like Kubernetes, it is best to have small-sized images to reduce the image transfer and deploy time.
How to Reduce Docker Image Size?
If we take a container image of a typical application, it contains a base image, Dependencies/Files/Configs, and cruft (unwanted software).
So it all boils down to how efficiently we can manage these resources inside the container image.
Let’s look at different established methods of optimizing Docker images. Additionally, we have given practical examples to understand docker image optimization in real time.
Either you use the examples given in the article or try the optimization techniques on existing Dockerfiles.
The following are the methods by which we can achieve docker image optimization.
- Using distroless/minimal base images
- Multistage builds
- Minimizing the number of layers
- Understanding caching
- Using Dockerignore
- Keeping application data elsewhere
Docker Excercise Files: All the application code, Dockerfiles, and configs used in this article are hosted this Github repository. You can clone it and follow along the tutorial.
Method 1: Use Minimal Base Images
Your first focus should be on choosing the right base image with a minimal OS footprint.
One such example is alpine base images. Alpine images can be as small as 5.59MB. It’s not just small; it’s very secure as well.
alpine latest c059bfaa849c 5.59MB
Nginx alpine base image is only 22MB.
By default, it comes with the sh shell that helps debug the container by attaching it.
You can further reduce the base image size using distroless images. It is a stripped-down version of the operating system. Distroless base images are available for java, nodejs, python, Rust, etc.
Distroless images are so minimal that they don’t even have a shell in them. So, you might ask, then how do we debug applications? They have the debug version of the same image that comes with the busybox for debugging.
Also, most of the distributions now have their minimal base images.
Note:You cannot directly use the publicly available base images in project environments. You need to get approval from the enterprise security team to use the base image. In some organizations, the security team itself publishes base images every month after testing & security scanning. Those images would be available in the common organization docker private repository.
Method 2: Use Docker Multistage Builds
The multistage build pattern is evolved from the concept of builder pattern where we use different Dockerfiles for building and packaging the application code. Even though this pattern helps reduce the image size, it puts little overhead when it comes to building pipelines.
In multistage build, we get similar advantages as the builder pattern. We use intermediate images (build stages) to compile code, install dependencies, and package files in this approach. The idea behind this is to eliminate unwanted layers in the image.
After that, only the necessary app files required to run the application are copied over to another image with only the required libraries, i.e., lighter to run the application.
Let’s see this in action, with the help of a practical example where we create a simple Nodejs application and optimize its Dockerfile.
First, let’s create the code. We will have the following folder structure.
├── Dockerfile1├── Dockerfile2├── env├── index.js└── package.json
Save the following as index.js
.
const dotenv=require('dotenv'); dotenv.config({ path: './env' });dotenv.config();const express=require("express");const app=express();app.get('/',(req,res)=>{ res.send(`Learning to Optimize Docker Images with DevOpsCube!`);});app.listen(process.env.PORT,(err)=>{ if(err){ console.log(`Error: ${err.message}`); }else{ console.log(`Listening on port ${process.env.PORT}`); } })
Save the following as package.json
.
{ "name": "nodejs", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "dotenv": "^10.0.0", "express": "^4.17.2" }}
Save the following port variable in a file named env
.
PORT=8080
A simple Dockerfile
for this application would like this – Save it as Dockerfile1
.
FROM node:16COPY . .RUN npm installEXPOSE 3000CMD [ "node", "index.js" ]
Let’s see the storage space that it requires by building it.
docker build -t devopscube/node-app:1.0 --no-cache -f Dockerfile1 .
After the build is complete. Let’s check its size using –
docker image ls
This is what we get.
devopscube/node-app 1.0 b15397d01cca 22 seconds ago 910MB
So the size is 910MBs
.
Now, let’s use this method to create a multistage build.
We will use node:16
as the base image, i.e., the image for all the dependencies & modules installation, after that, we will move the contents into a minimal and lighter ‘alpine
‘ based image. The ‘alpine
‘ image has the bare minimum utilities & hence is very light.
Here is a pictorial representation of a Docker multistage build.
Also, in a single Dockerfile
, you can have multiple stages with different base images. For example, you can have different stages for build, test, static analysis, and package with different base images.
Let’s see what the new Dockerfile might look like. We are just copying over the necessary files from the base image to the main image.
Save the following as Dockerfile2
.
FROM node:16 as buildWORKDIR /appCOPY package.json index.js env ./RUN npm installFROM node:alpine as mainCOPY --from=build /app /EXPOSE 8080CMD ["index.js"]
Let’s see the storage space that it requires by building it.
docker build -t devopscube/node-app:2.0 --no-cache -f Dockerfile2 .
After the build is complete. Let’s check its size using
docker image ls
This is what we get.
devopscube/node-app 2.0 fa6ae75da252 32 seconds ago 171MB
So the new reduced image size is 171MBs as compared to the image with all dependencies.
That’s an optimization of over 80%!
However, if we would have used the same base image we used in the build stage, we wouldn’t see much difference.
You can further reduce the image size using distroless images. Here is the same Dockerfile
with a multistage build step that uses the google nodeJS distroless image instead of alpine.
FROM node:16 as buildWORKDIR /appCOPY package.json index.js env ./RUN npm installFROM gcr.io/distroless/nodejsCOPY --from=build /app /EXPOSE 3000CMD ["index.js"]
If you build the above Dockerfile, your image will be 118MB,
devopscube/distroless-node 1.0 302990bc5e76 118MB
Method 3: Minimize the Number of Layers
Docker images work in the following way – each RUN, COPY, FROM
Dockerfile instructions add a new layer & each layer adds to the build execution time & increases the storage requirements of the image.
Let’s see this in action, with the help of a practical example: let’s create a ubuntu image with updated & upgraded libraries, along with some necessary packages installed such as vim, net-tools, dnsutils.
A Dockerfile
to achieve this would be as follows – Save this as Dockerfile3
.
FROM ubuntu:latestENV DEBIAN_FRONTEND=noninteractiveRUN apt-get update -yRUN apt-get upgrade -yRUN apt-get install vim -yRUN apt-get install net-tools -yRUN apt-get install dnsutils -y
We wish to also look into the build time for this image.
The Docker daemon has an in-built capability to display the total execution time that a Dockerfile is taking.
To enable this feature, take the following steps –
- Create a
daemon.json
file with the following contents at/etc/docker/
{ "experimental": true}
2. Execute the following command to enable the feature.
export DOCKER_BUILDKIT=1
Let’s build it and see the storage & build time.
time docker build -t devopscube/optimize:3.0 --no-cache -f Dockerfile3 .
It would display the execution times in the terminal.
time docker build -t devopscube/optimize:3.0 --no-cache -f Dockerfile3 .[+] Building 117.1s (10/10) FINISHED => [internal] load build definition from Dockerfile .... => => writing image sha256:9601bcac010062c656dacacbc7c554b8ba552c7174f32fdcbd24ff9c7482a805 0.0s => => naming to docker.io/devopscube/optimize:3.0 0.0s real 1m57.219s user0m1.062ssys0m0.911s
After the build is complete – the execution time comes to be 117.1 seconds.
Let’s check its size using
docker image ls
This is what we get.
devopscube/optimize 3.0 9601bcac0100 About a minute ago 227MB
So the size is 227MBs.
Let’s combine the RUN commands into a single layer & save it as Dockerfile4.
FROM ubuntu:latestENV DEBIAN_FRONTEND=noninteractiveRUN apt-get update -y && apt-get upgrade -y && apt-get install --no-install-recommends vim net-tools dnsutils -y
In the above RUN command, we have used --no-install-recommends
flag to disable recommended packages. It is recommended whenever you use install
in your Dockerfiles
Let’s see the storage & build time required by building it.
time docker build -t devopscube/optimize:4.0 --no-cache -f Dockerfile4 .
It would display the execution times in the terminal.
time docker build -t devopscube/optimize:0.4 --no-cache -f Dockerfile4 .[+] Building 91.7s (6/6) FINISHED => [internal] load build definition from Dockerfile2 0.4s... => => naming to docker.io/devopscube/optimize:4.0 0.0s real 1m31.874s user0m0.884ssys0m0.679s
After the build is complete – the execution time comes to be 91.7 seconds.
Let’s check its size using
docker image ls
This is what we get.
devopscube/optimize 4.0 37d746b976e3 42 seconds ago 216MB
So the size is 216MBs.
Using this optimization technique, the execution time was reduced from 117.1s to 91.7s & the storage size was reduced from 227MBs to 216MBs.
Method 4: Understanding Caching
Often, the same image has to be rebuilt again & again with slight modifications in code.
Docker helps in such cases by storing the cache of each layer of a build, hoping that it might be useful in the future.
Due to this concept, it’s recommended to add the lines which are used for installing dependencies & packages earlier inside the Dockerfile – before the COPY commands.
The reason behind this is that docker would be able to cache the image with the required dependencies, and this cache can then be used in the following builds when code gets modified.
For example, let’s take a look at the following two Dockerfiles.
Dockerfile5
FROM ubuntu:latestENV DEBIAN_FRONTEND=noninteractiveRUN apt-get update -yRUN apt-get upgrade -yRUN apt-get install vim -yRUN apt-get install net-tools -yRUN apt-get install dnsutils -yCOPY . .
Dockerfile6
FROM ubuntu:latestCOPY . .ENV DEBIAN_FRONTEND=noninteractiveRUN apt-get update -yRUN apt-get upgrade -yRUN apt-get install vim -yRUN apt-get install net-tools -yRUN apt-get install dnsutils -y
Docker would be able to use the cache functionality better with Dockerfile5
than Dockerfile6
due to the better placement of the COPY command.
Method 5: Use Dockerignore
As a rule, only the necessary files need to be copied over the docker image.
Docker can ignore the files present in the working directory if configured in the .dockerignore
file.
This feature should be kept in mind while optimizing the docker image.
Method 6: Keep Application Data Elsewhere
Storing application data in the image will unnecessarily increase the size of the images.
It’s highly recommended to use the volume feature of the container runtimes to keep the image separate from the data.
Docker Image Optimization Tools
Following are some of the open-source tools that will help you optimize
- Dive: It is an image explorer tool that helps you discover layers in the Docker & OCI containers images. Using dive, you can find ways to optimize your Docker images. Check out the Dive Github repo for more details.
- Docker Slim: It helps you optimize your Docker images for security and size. Check out the Docker Slim Github repo for more details.
I will keep adding tools to this list.
Summary
The above methods should help you build optimized Docker images and write better Dockerfiles.
Also, If you follow all the standard container best practices, you can reduce the docker image size to have lightweight image deployments.
If you are getting started with your Docker journey, you can check out my article on 3 methods to run docker in docker.
If you are learning container orchestration using Kubernetes, check out comprehensive Kubernetes tutorials for beginners.
FAQs
How will you reduce the size of docker image? ›
Multistage builds feature in Dockerfiles enables you to create smaller container images with better caching and a smaller security footprint. With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build.
Which tool reduce docker image size? ›Dive is an open-source tool for exploring a Docker image and its layer contents, then discovering ways to shrink the size of your Docker/OCI image. At a high level, it works by analyzing the layers of a Docker image.
How do I reduce the number of layers in docker image? ›- Use multi-stage builds.
- Use a base image with fewer layers.
- Combine multiple COPY and ADD instructions.
- Combine multiple RUN instructions.
- Select the picture you want to compress.
- Click the Picture Tools Format tab, and then click Compress Pictures.
- Do one of the following: To compress your pictures for insertion into a document, under Resolution, click Print. ...
- Click OK, and name and save the compressed picture somewhere you can find it.
- Prune images. The docker image prune command allows you to clean up unused images. ...
- Prune containers. When you stop a container, it is not automatically removed unless you started it with the --rm flag. ...
- Prune volumes. ...
- Prune networks. ...
- Prune everything.
You can specify your size as: Raw number of bytes: 1024. Human-readable megabytes: 30 MB or 30 MiB. Human-readable gigabytes: 1 GB or 1 GiB.
How to get uncompressed image size in docker? ›To measure the uncompressed size of a Docker image, it is easiest to pull the image locally. You can then retrieve the size of the image with the docker inspect command.
How to flatten an image in docker? ›Use the docker-squash command to create a new, flattened image from the original image. Replace <image_id> with the ID of the image you want to flatten, and <image_name> with the desired name for the new image.
Why reduce docker layers? ›Building Docker images may be easy, but sometimes people make the mistake of creating overly large images without optimizing them. Smaller images are faster to transfer and deploy.
Why my Docker image is too big? ›Often a Docker image becomes large simply because your app needs to download and compile dependencies used by your app. The image size increases due to unneeded files & folders that you only need to build/install, but not to run your application.
How to make Docker image build faster? ›
The easiest way to increase the speed of your Docker image build is by specifying a cached image that can be used for subsequent builds. You can specify the cached image by adding the --cache-from argument in your build config file, which will instruct Docker to build using that image as a cache source.
How do I reduce the size of an image without losing quality? ›- Use the WordPress Library to Resize Images. ...
- Use A WordPress Plugin to Resize Images. ...
- Use a Desktop App to Resize Images. ...
- Use an Online Platform to Resize Images. ...
- Use Design Software to Resize Your Images.
- Consolidate your images. Place all of the images you want to resize in one folder.
- Import to start. Select Import and then select your images.
- Export to resize. ...
- Select your image width and height. ...
- Choose your location.
- Stop the container(s) using the following command: docker-compose down.
- Delete all containers using the following command: docker rm -f $(docker ps -a -q)
- Delete all volumes using the following command: docker volume rm $(docker volume ls -q)
- Restart the containers using the following command:
Sadly, the only solution that works is to go into Docker Desktop, click on the troubleshoot button, and hit the Clean / Purge data button.
How to check docker container storage size? ›The docker system df command displays information regarding the amount of disk space used by the docker daemon.
How to check size of all docker images? ›To view the approximate size of a running container, you can use the command docker container ls -s . Running docker image ls shows the sizes of your images.
Does image size matter in docker? ›Smaller Docker images take up less disk space. A Docker image is composed of reusable layers, i.e. different images might share some layers. Because of such architecture, image size and disk consumption are not necessarily directly correlated.
What is the most uncompressed image format? ›BMP or Bitmap Image File is a format developed by Microsoft for Windows. There is no compression or information loss with BMP files which allow images to have very high quality, but also very large file sizes.
What is the default disk image size for Docker? ›The default value is 10GB. This value can be changed to allow the container to have more size using dockerd --storage-opt dm.
How to reduce docker image size stackoverflow? ›
Adding rm -rf /var/lib/apt/lists/* after apt-get update will reduce image size by removing all useless apt-get stuff. You may also remove vim from your image in the last RUN instruction.
How to manipulate docker image? ›- To begin, create a Docker container from a parent image from the repository.
- Then, using the bash shell, connect to the container. docker exec -it container-name bash.
- Make the necessary changes to the container from the shell. ...
- Exit the container once the changes have been completed.
The actual amount of data that's pushed will be compressed before sending, so the uploaded size will not be reflected by the progress bar.
Are docker images compressed? ›docker images are compressed by default. you will notice when running docker pull , where it will download the needed images\layers and then extract\decompress them. there is no need for you to compress the files within your docker images.
Does Docker squash reduce image size? ›Yes, the Docker squash reduces the image size. Docker squash is one of the famous Docker utilities used to build smaller size Docker images by squashing the Docker multiple layers into one fewer layer.
Can a Docker container run out of space? ›By default, a container has no resource constraints and can use as much of a given resource as the host's kernel scheduler allows. Docker provides ways to control how much memory, or CPU a container can use, setting runtime configuration flags of the docker run command.
How many layers is too many in Docker? ›This thin read-write layer is what allows us to modify files in a running Docker container. The maximum number of layers an image can have is 127, provided your underlying storage driver supports it. Let's see that with a sample Dockerfile.
What is difference between Docker image and Docker layer? ›An image is a file that represents a packaged application with all the dependencies needed to run correctly. In other words, we could say that a Docker image is like a Java class. Images are built as a series of layers. Layers are assembled on top of one another.
What is the difference between Dockerfile and Docker image? ›A Dockerfile is the Docker image's source code. A Dockerfile is a text file containing various instructions and configurations. The FROM command in a Dockerfile identifies the base image from which you are constructing. When you run the Docker run command, Docker uses this file to build the image itself.
How do I delete large images in docker? ›The -f flag is used to remove the running Docker containers forcefully. The docker images -qa will return the image id of all the Docker images. The docker rmi command will then remove all the images one by one. Again, the -f flag is used to forcefully remove the Docker image.
How to give docker image more memory? ›
Set Maximum Memory Access
To limit the maximum amount of memory usage for a container, add the --memory option to the docker run command. Alternatively, you can use the shortcut -m . Within the command, specify how much memory you want to dedicate to that specific container.
Answer: Use the CSS max-width Property
You can simply use the CSS max-width property to auto-resize a large image so that it can fit into a smaller width <div> container while maintaining its aspect ratio.
Adding rm -rf /var/lib/apt/lists/* after apt-get update will reduce image size by removing all useless apt-get stuff. You may also remove vim from your image in the last RUN instruction.
How do I reduce the size of a Docker image in react JS? ›- Choose the right base image.
- Install production dependencies only.
- Use multi stage build.
- Copy required files only.
- Prune node_modules.
- Using the code "Docker images", you can check the Docker image size. ...
- Ensure the file size is not that long.
- Use the code "Docker build -t [Docker image name]" to show context size.
- Create a file with ". ...
- Add the unwanted files to the newly created file. ...
- Removal of Interdependencies.
You can specify your size as: Raw number of bytes: 1024. Human-readable megabytes: 30 MB or 30 MiB. Human-readable gigabytes: 1 GB or 1 GiB.
How to reduce the size of an image without changing dimensions? ›You can resize your pictures and images without changing their quality. There is no need to install any additional software on your computer to make Simple Image Resizer do its job. You simply browse go to www.simpleimageresizer.com and upload the images you want to shrink.
Why is my Docker image size so large? ›Often a Docker image becomes large simply because your app needs to download and compile dependencies used by your app. The image size increases due to unneeded files & folders that you only need to build/install, but not to run your application.
How do I reduce the size of a bulk image? ›- Consolidate your images. Place all of the images you want to resize in one folder.
- Import to start. Select Import and then select your images.
- Export to resize. ...
- Select your image width and height. ...
- Choose your location.
To measure the uncompressed size of a Docker image, it is easiest to pull the image locally. You can then retrieve the size of the image with the docker inspect command. You can see that the uncompressed size of the mcr.microsoft.com/dotnet/core/samples:dotnetapp-buster-slim image is 189,371,468 bytes (185 MB).
How do I optimize an image size in React? ›
To optimize images in React, you can use Webpack - specifically the webpack-image-loader module. By adding this module and configuring it correctly in your webpack. config. js file, you can tell React to serve compressed images from the public folder.
How to decrease image size in js? ›Answer: Use the JavaScript width and height property
You can use either width or height JavaScript property to proportionally increase and decrease the dimension of an image like zoom-in and zoom-out feature.
By analyzing the bundle, identifying the largest components, and implementing strategies such as using smaller libraries, code splitting, lazy loading, tree shaking, minification, and compression, developers can reduce React bundle size and improve performance.