https://medium.com/@ksaquib/how-i-cut-docker-image-size-by-90-best-practices-for-lean-containers-1f705cead02b
How I Cut Docker Image Size by 90%: Best Practices for Lean Containers | by Saquib Khan | Medium
Reducing Docker image sizes is crucial for streamlining development workflows, speeding up builds, and minimizing deployment times, all while saving valuable storage space. Drawing from my own experience, I’ve discovered several effective strategies that not only optimize Docker images but also improve overall performance and efficiency. Here’s a guide to the best practices I’ve used and highly recommend for maintaining lean, efficient Docker images.
1. Use a Minimal Base Image
Selecting a minimal base image is one of the most effective ways to reduce Docker image size. Minimal base images, such as alpine, scratch, or debian-slim, are significantly smaller than larger base images like ubuntu or debian, as they come with only the essentials.
Example with Python
Consider the difference in size between a typical ubuntu-based Python image and an alpine-based Python image:
Using Ubuntu as Base Image:
FROM python:3.11-slim
- Image Size: Approximately 60 MB (Python 3.11 with Ubuntu base image)
Using Alpine as Base Image:
FROM python:3.11-alpine
- Image Size: Approximately 23 MB (Python 3.11 with Alpine base image)
The Alpine-based image is around 3 times smaller than the Ubuntu-based image. This significant reduction in size is due to Alpine Linux being a minimal distribution specifically designed for Docker environments. Using such minimal base images not only reduces the image size but also decreases the attack surface, enhancing security.
2. Multistage Builds
Multistage builds allow you to separate the build environment from the runtime environment, ensuring that only the essential files make it into the final image. This approach helps in reducing the size of the final Docker image by excluding build tools and dependencies that are not needed at runtime.
Example with Python
Consider a Python application where you want to use multistage builds to keep the final image lean:
Multistage Build Dockerfile:
# Build stage
FROM python:3.11-slim AS builder
WORKDIR /app
# Install build dependencies
COPY requirements.txt .
RUN pip install --user -r requirements.txt
# Copy application code
COPY . .
# Final stage
FROM python:3.11-slim
WORKDIR /app
# Install only runtime dependencies
COPY --from=builder /root/.local /root/.local
COPY . .
# Set the path to include user-installed packages
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
Size Comparison
- Without Multistage Builds: If you use a single stage Dockerfile, the final image would include both the build dependencies and the application code. For example:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["python", "app.py"]
- Image Size: Approximately 150 MB (includes both build and runtime dependencies).
With Multistage Builds: Using the multistage build example provided, the final image is significantly smaller:
- Image Size: Approximately 60 MB (contains only runtime dependencies and application code).
3. Remove Unnecessary Files
Cleaning up unnecessary files such as cache, temporary files, and build dependencies is a crucial step in reducing Docker image size. This practice ensures that your image contains only the essential components required for running your application, while minimizing the size and potential attack surface.
Example with Python
Here’s an example of how to remove unnecessary files in a Dockerfile for a Python application:
Before Cleanup:
FROM python:3.11-slim
WORKDIR /app
# Install build dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy application code
COPY . .
CMD ["python", "app.py"]
With Cleanup:
FROM python:3.11-slim
WORKDIR /app
# Install build dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt \
# Clean up temporary files and caches
&& rm -rf /root/.cache/pip
# Copy application code
COPY . .
CMD ["python", "app.py"]
Without Cleanup: In a Dockerfile where unnecessary files are not removed, the size of the image can be larger due to leftover cache and temporary files:
- Image Size: Approximately 150 MB (includes build caches and unnecessary files).
With Cleanup: Using cleanup commands such as rm -rf /root/.cache/pip to remove caches and temporary files can reduce the final image size:
- Image Size: Approximately 120 MB (after cleaning up caches and temporary files).
4. Use .dockerignore File
The .dockerignore file functions similarly to a .gitignore file but for Docker builds. It specifies which files and directories should be excluded from the Docker build context. This helps in reducing the size of the build context, leading to faster builds and smaller Docker images.
Benefits of Using .dockerignore
- Reduced Build Context Size: By excluding unnecessary files, you minimize the amount of data sent to the Docker daemon, speeding up the build process.
- Smaller Docker Images: Excluding files that are not needed in the final image prevents them from being included, which helps in keeping the image size down.
- Improved Build Efficiency: Smaller build contexts mean Docker can cache layers more effectively, leading to faster rebuilds.
Example .dockerignore File
Here’s a simple example of a .dockerignore file:
.git
node_modules
*.log
.DS_Store
Without **.dockerignore**:
- When unnecessary files are included in the Docker build context, they are sent to the Docker daemon and become part of the Docker image, even if they are not used in the final image.
- For example, including a
.gitdirectory ornode_modulesfolder can significantly increase the size of the build context. The.gitdirectory can contain several hundred megabytes of version history, whilenode_modulesmight add another several hundred megabytes of dependencies that are not needed in the production image. -
Impact on Build Context Size: Excluding files and directories that are not needed in the final image helps reduce the build context size, which can otherwise add up to several gigabytes, depending on the size and number of excluded files. Approximately t he build context might be around 1 GB if it includes a large
.gitdirectory,node_modules, and other files not needed in the image.
With **.dockerignore**:
- By using a
.dockerignorefile to exclude unnecessary files, you limit the build context to only those files that are essential for the application. - This exclusion can lead to a significantly smaller build context. For example, excluding
.git,node_modules, and other large directories can reduce the context size from several gigabytes to just a few megabytes. -
Impact on Image Size: Although the
.dockerignorefile itself doesn’t directly reduce the size of the final Docker image, it does prevent unnecessary files from being added to the build context. This results in a more efficient build process and helps in creating a leaner final image by ensuring that only relevant files are included. After excluding these unnecessary files, the build context might be reduced to 50 MB, which can significantly decrease build times and make the final Docker image more efficient.
5. Minimize Layers
In Docker, each RUN, COPY, and ADD instruction in your Dockerfile creates a new layer in the resulting image. These layers can increase the overall size of the Docker image and impact build performance. Combining commands into a single RUN instruction helps in minimizing the number of layers, resulting in a more efficient and compact Docker image.

Without Layer Minimization:
- Each individual instruction (
RUN,COPY, etc.) creates a new layer in the Docker image. These layers accumulate and can lead to a larger image size due to intermediate files, temporary data, and additional metadata. - For example, using separate
RUNinstructions can result in multiple layers, each adding its own metadata and overhead, which can bloat the final image size. -
Impact on Image Size: If you use multiple
RUNinstructions like:
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
Using multiple RUN instructions can lead to an image size of around 150 MB, with each layer adding overhead.
With Layer Minimization:
- Combining commands into a single
RUNinstruction reduces the number of layers and helps in consolidating changes into fewer, more optimized layers. - For example, combining the commands into one
RUNinstruction:
RUN apt-get update && apt-get install -y curl && apt-get clean
Combining commands into a single RUN instruction can reduce the image size to approximately 130 MB. This reduction is achieved by consolidating changes into fewer layers and minimizing unnecessary intermediate data.
6. Using Specific COPY Commands:
Instead of copying entire directories into your Docker image, use specific COPY commands to include only the files you need. This approach avoids transferring unnecessary files, reducing the image size.
Example:
COPY package.json .
COPY src/ src/
- Size Impact: By copying only specific files and directories, you avoid including unwanted files that can bloat your image. For example, excluding development files or build artifacts can reduce the image size by several megabytes, depending on the size of the excluded data.
7. Use Multi-Arch Images
Creating multi-architecture Docker images ensures compatibility with various environments (e.g., ARM, x86). This approach optimizes images for different hardware platforms.
Example:
- Size Impact: Multi-arch images are optimized for specific architectures, potentially reducing the size of images used on different platforms. This helps in avoiding bloated images that include support for multiple architectures when only one is needed.
Conclusion
These best practices lead to more efficient, secure, and faster Docker images, enhancing your overall container management and deployment processes.