Docker images are easy to build and easy to neglect. A naive Dockerfile produces images that are gigabytes in size, take minutes to pull, and contain build tools, package caches, and temporary files that have no place in production. This project explores Docker image optimization systematically — with real measurements showing the size reduction at each step.
Why Image Size Matters
- Deployment speed — a 1.2GB image takes 4× longer to pull than a 300MB image across your CI/CD pipeline and every server deployment
- Security surface — every package in an image is a potential vulnerability. Fewer packages means fewer CVEs
- Cost — registry storage and network egress both cost money at scale
- Environmental impact — smaller images consume less bandwidth and compute — real energy savings at scale
Baseline — What an Unoptimized Image Looks Like
# Naive Dockerfile
FROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
Optimization 1 — Slim Base Image
FROM python:3.11-slim # replaces python:3.11
Optimization 2 — Multi-Stage Build
The biggest win. Build dependencies are only needed during compilation — they don't belong in the production image. Multi-stage builds let you compile in one stage and copy only the output to the final image.
# Stage 1: build
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: production — no build tools
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY app.py .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
Optimization 3 — Alpine Base
FROM python:3.11-alpine
Layer Caching — Build Speed Optimization
Docker caches each layer. If a layer hasn't changed, Docker reuses the cache. Copy dependencies before application code — dependencies change less often than app code, so the expensive pip install layer gets cached between most builds.
# Wrong order — cache busted on every code change
COPY . .
RUN pip install -r requirements.txt
# Right order — pip install cached unless requirements.txt changes
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
Results Summary
Key Lessons
- Always use slim or alpine base images unless you have a specific reason not to
- Multi-stage builds are the single most impactful optimization — use them for any compiled or dependency-heavy app
- Layer order matters for caching — slow layers go first, fast-changing layers go last
- Add a
.dockerignorefile — stop copying.git,node_modules, and test files into images