When I started working with Docker, I made the same
mistake most beginners make — I used the full base image
and copied everything into the container without thinking
about size or performance.
The result? A 1.12 GB image for a simple Flask app.
Here's exactly what I changed to bring it down to 131 MB.
The bad Dockerfile (what most tutorials show you)
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
Problems with this:
-
python:3.11is ~950 MB — includes compilers and tools your app will never use -
COPY . .before installing dependencies kills layer caching — pip reinstalls everything on every single code change - No
.dockerignore— copies your.gitfolder,__pycache__, and other junk into the image - Flask dev server is not suitable for production
The optimized Dockerfile
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
What changed and why
1. Slim base image
Switching from python:3.11 to python:3.11-slim
removes all the build tools and system packages your
app doesn't need in production. This alone cuts
hundreds of MB.
2. Multi-stage build
The builder stage installs all dependencies. The final
stage copies only the installed packages — not pip
cache, not build tools, nothing extra. Only what your
app needs to run.
3. Layer caching
By copying requirements.txt first and installing
dependencies before copying the rest of the code,
Docker caches the dependency layer. When you change
your code, Docker skips the pip install step entirely
— builds go from 45 seconds to under 5 seconds.
4. .dockerignore
Create a .dockerignore file to exclude:
.git
pycache
*.pyc
.env
.venv
5. Production server
Replace Flask's dev server with Gunicorn — a
production-grade WSGI server that handles real traffic.
The results
| Before | After | |
|---|---|---|
| Base image | python:3.11 | python:3.11-slim |
| Build strategy | Single stage | Multi-stage |
| Final size | 1.12 GB | 131 MB |
| Reduction | — | 88% smaller |
Same app. Same functionality. 88% smaller image.
Why does image size matter?
- Faster deployments — smaller images pull faster from your registry to your server
- Lower costs — Docker Hub and cloud registries charge for storage
- Security — fewer packages means fewer potential vulnerabilities
- Faster CI/CD — your pipeline runs faster when it doesn't have to pull a 1 GB image every time
Try it yourself
The full code is on my GitHub:
Dockerfile Optimization
A practical demonstration of Docker image optimization techniques — reducing a Python Flask image from 1.12 GB down to 131 MB (88% reduction) using multi-stage builds, slim base images, and Docker best practices.
Results
| Base image | Final size | Build time | |
|---|---|---|---|
| Before | python:3.11 |
1.12 GB | slow (no cache) |
| After |
python:3.11-slim + multi-stage |
131 MB | fast (layer cache) |
88% smaller image — faster pulls, lower cloud storage costs, reduced attack surface.
What changed and why
1. Base image — python:3.11 → python:3.11-slim
The full python:3.11 image ships with compilers, build tools, and system packages your app will never use in production. The slim variant contains only what's needed to run Python.
# Before — 1.12 GB
FROM python:3.11
# After — starts at ~130 MB
FROM python:3.11-slim
2. Multi-stage build
The builder stage installs dependencies. The final stage copies only the installed packages — leaving behind pip cache, build tools…
Build both images and compare:
docker build -f Dockerfile.bad -t flask-bad .
docker build -f Dockerfile.optimized -t flask-optimized .
docker images | grep flask
If you need help Dockerizing your own Python or Node.js
app or setting up Docker Compose for your stack, I offer
this as a freelance service on Fiverr:
https://www.fiverr.com/s/Q7m3l1p
Happy to answer any questions in the comments.
Top comments (0)