Optimizing Docker Image with Multi-Stage Builds: Reducing Image Size by 99%

Optimizing Docker Image with Multi-Stage Builds: Reducing Image Size by 99%

I’ve been working with Docker for a while, and recently I ran into a problem with my Docker image size. One of my images was around 1GB, which felt way too big! 😬 A large image means slower deployment, more storage space, and overall just not efficient.

So, I decided to fix this by using a multi-stage Dockerfile, and guess what? I reduced the image size by 99%! 🎉

😓 The Problem with Traditional Dockerfiles

A normal Dockerfile is pretty straightforward. You start with a base image, install your dependencies, build the application, and then run it. But all this stuff stays in the image, even the things you don’t need after the app is built. This makes the image larger than it needs to be.

Here’s what a traditional Dockerfile might look like:

# Traditional Dockerfile example
FROM python:3.9

WORKDIR /app/backend

COPY requirements.txt /app/backend
RUN pip install -r requirements.txt

COPY . /app/backend

EXPOSE 8000

CMD python /app/backend/manage.py runserver 0.0.0.0:8000

In this Dockerfile, we’re:

  • Using the full python image (which is quite big),

  • Installing all the dependencies,

  • And keeping everything in the final image, even the build tools, which we don’t need once the app is built.

As a result, you end up with a pretty big image that’s not optimized at all. 😕

🎯 Enter Multi-Stage Builds: A Game-Changer

Now, let me tell you about multi-stage builds. This approach allows you to split the Dockerfile into stages. In the first stage, you do all the heavy work like installing dependencies and building the app. In the final stage, you only copy over what’s needed to run the app—nothing else.

Here’s an example of a multi-stage Dockerfile:

# Stage 1: Build the application
FROM python:3.9 AS build

WORKDIR /app/backend

COPY requirements.txt /app/backend
RUN pip install -r requirements.txt

COPY . /app/backend

# Stage 2: Create the final image with only the necessary artifact
FROM python:3.9-slim

COPY --from=build /app/backend /app/backend
# Expose the application port
EXPOSE 8000
# Command to run the application
CMD python /app/backend/manage.py runserver 0.0.0.0:8000

How it works:

  • Stage 1: I’m using the python:3.9 image to build the app. This includes installing all dependencies and running the build process. 🛠️

  • Stage 2: In this stage, I switch to a much lighter image (python:3.9-slim) and copy only the built files from the first stage. This makes the final image way smaller! 📦

🔥 The Results:

By using multi-stage builds, I was able to reduce my Docker image size from 1GB to just 100MB! 😱 That’s a 90% reduction! Not only did this save space, but it also made my deployments much faster! ⚡

Wanna Know Something More Awesome? 🤩

You can reduce the size of this image even more by using scratch as a base image! The scratch image is completely empty, which means that when you use it, you only include what you absolutely need. This can shrink your image size from 1GB to just 10MB—almost a 99% reduction! 😲

Here’s how you can modify the multi-stage Dockerfile to use scratch:

# Stage 1: Build the application
FROM python:3.9 AS build

WORKDIR /app/backend

COPY requirements.txt /app/backend
RUN pip install -r requirements.txt

COPY . /app/backend

# Stage 2: Create the final image with only the necessary artifact
FROM scratch
COPY --from=build /app/backend /app/backend
# Expose the application port
EXPOSE 8000
# Command to run the application
CMD python /app/backend/manage.py runserver 0.0.0.0:8000

Why You Should Try Multi-Stage Builds:

  1. Smaller Image Sizes 📉: Since you’re only copying what’s necessary, your final image stays lean.

  2. Faster Deployments 🚀: Smaller images are quicker to deploy, meaning less waiting time.

  3. Improved Security 🔒: A smaller image has fewer components, which reduces potential vulnerabilities.

  4. Lower Costs 💰: Less storage space means lower costs, especially in large-scale environments.

💭 Final Thoughts

If you’ve been dealing with large Docker images, give multi-stage builds a try. It’s easy to set up, and the results are worth it. You’ll have faster deployments, smaller images, and fewer headaches! 🧠

I’m still learning and experimenting with Docker, so feel free to share your tips or experiences in the comments! 😊

Did you find this article valuable?

Support Som Pandey's blog by becoming a sponsor. Any amount is appreciated!