Docker is an incredible tool for containerizing applications but bloated images can slow down deployments, increase storage costs, and even introduce security risks.
A few months ago, I discovered that one of my Docker images was over 1.2GB. Ridiculous for a simple Python microservice!
After some research and experimentation, I managed to shrink it down to just 230MB (an 80% reduction without sacrificing functionality) Here’s how I did it.
The Problem: Why Are Docker Images So Big?
When I first ran:
docker images
I saw my image was massive:
REPOSITORY TAG SIZE
my-python-app latest 1.21GB
This was shocking. My app was just a Flask API with a few dependencies. So, I dug deeper.
Common Culprits of Docker Bloat
-
Using a heavy base image (
python:3.9
instead ofpython:3.9-slim
) - Including unnecessary files (cache, logs, dev dependencies)
- Not using multi-stage builds (leaving build-time dependencies in the final image)
-
Copying entire directories (
.git
,__pycache__
,node_modules
, etc.)
The Solution: Optimizing the Dockerfile
1. Start With a Slimmer Base Image
Originally, I used:
FROM python:3.9
This pulls the full Python image, which includes many tools I didn’t need (like gcc
, make
, etc.). I switched to:
FROM python:3.9-slim
This alone reduced the image size by over 50%.
2. Use Multi-Stage Builds
For packages that require build tools (like gcc
), use a multi-stage build to keep them out of the final image:
# Stage 1: Build dependencies
FROM python:3.9 as builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: Runtime image
FROM python:3.9-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY app.py .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
Now, the final image has only the necessary dependencies, no compilers or build tools.
3. Clean Up After Installing Packages
Even with --no-cache-dir
, it's a good idea to remove temporary files if your build process creates any:
RUN rm -rf /tmp/* /var/tmp/*
In most cases, this step is optional but good hygiene if you're generating any custom build artifacts.
4. Be Selective With COPY
Instead of:
COPY . .
Use:
COPY app.py requirements.txt /app/
This avoids accidentally including large or sensitive files like .git
, .env
, or __pycache__
.
5. Use a .dockerignore
File
To make sure unnecessary files don’t sneak into the build context, use a .dockerignore
file:
.git
__pycache__
*.log
.env
You can exclude things like README.md
or Dockerfile
too, but only if you know they're not needed during the build.
The Final Result
After applying these changes:
docker images
Now shows:
REPOSITORY TAG SIZE
my-python-app latest 230MB
From 1.2GB to 230MB: an 80% reduction.
Key Takeaways
- Use
-slim
or-alpine
base images when possible. - Multi-stage builds keep your final image clean.
- Clean up caches and temp files when necessary.
- Be deliberate with what you
COPY
. - Use
.dockerignore
to avoid accidental bloat.
Smaller images mean faster deployments, lower cloud costs, and better security (fewer packages = smaller attack surface).
Top comments (0)