- Published on
How I Reduced My Docker Image Size by 78% with Multi-Stage Builds
- Authors

- Name
- Sneha Tuladhar
- @
Table of Contents
- Introduction
- Why Multi-Stage Docker Builds Matter
- How Multi-Stage Builds Work
- How Multi-Stage Docker Builds Reduced My Image Size from 7.97GB to 1.78GB
- The "Before" - Monolithic Dockerfile
- The "After" - Multi-Satge Dockerfile
- Key Optimization Strategies
- Outcome
- Results Comparison
- Key Takeaways
- Conclusion
Introduction
Optimizing Docker images is one of the most overlooked parts of application deployment.
When your builds grow large, everything becomes slower:
- CI/CD pipelines
- Deployment rollouts
- Container start time
- Image pulls from ECR/Docker Hub
This blog explains:
✔ Why multi-stage builds are essential
✔ How I applied them for frontend & backend
✔ How choosing the right packages reduces image size
Why Multi-Stage Docker Builds Matter
Multi-stage Docker builds are crucial for creating efficient and secure production images. In this approach, the application is built in one stage, and only the necessary output is copied to the final image, keeping it minimal and lightweight. Build tools, compilers, and caches, such as node_modules or pip caches, remain in the build stage and are excluded from the production image.
Benefits include:
- Smaller images → faster pushes/pulls and deployments
- Improved security → dev tools and compilers are not shipped
- Reproducible builds → isolated and deterministic stages
How Multi-Stage Builds Work
Builder Stage
- Compile your code
- Install dev dependencies and build tools
- Pre-download large models (e.g., Transformer embeddings)
Production Stage
- Copy only what's required to run the application
- Include runtime dependencies only
- Exclude compilers, caches, and dev tools
How Multi-Stage Docker Builds Reduced My Image Size from 7.97GB to 1.78GB
Initially, my backend Docker image was 7.97GB. Deployments were slow, storage costs high, and CI/CD pipelines cumbersome. Using multi-stage builds, I reduced it to 1.78GB — a 78% reduction.
The "Before" - Monolithic Dockerfile
# backend.Dockerfile.old
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y gcc g++ curl git build-essential python3-dev
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')"
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
The "After" - Multi-Satge Dockerfile
# backend.Dockerfile.prod
# Stage 1: Builder
FROM python:3.11-slim as builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
# Copy requirements first for better caching
COPY requirements.txt .
# Create virtual environment
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install dependencies with CPU-only PyTorch
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir numpy==1.24.3 && \
pip install --no-cache-dir torch==2.1.0 --index-url https://download.pytorch.org/whl/cpu && \
pip install --no-cache-dir -r requirements.txt
# Stage 2: Runtime
FROM python:3.11-slim
WORKDIR /app
# Copy only virtual environment from builder
COPY /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy application code
COPY app.py ./
COPY generate_summaries.py ./
COPY document_manifest.json ./
COPY patient_summary_cache.json ./
# Create directories for volumes
RUN mkdir -p data index_storage
# Install runtime dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
# Pre-download embedding model
RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')"
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Key Optimization Strategies
- Separation of Build and Runtime
- Builder stage contains all development tools and compilers (e.g., GCC, G++).
- Runtime stage includes only the dependencies and code necessary to run the application, reducing image size and improving security.
- Virtual Environment Copy
- Install Python packages and pre-download models in the builder stage.
- Copy the entire virtual environment to the runtime stage instead of reinstalling dependencies:
# Copy only virtual environment from builder
COPY /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
3.Minimal Base Images
- Using slim Python variants
- Alpine Linux for Node.js
- Nginx for serving static files
- Layer Optimization
# Copy requirements first for better caching
COPY requirements.txt .
# Combined RUN commands to reduce layers
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir numpy==1.24.3 && \
pip install --no-cache-dir torch==2.1.0 --index-url https://download.pytorch.org/whl/cpu && \
pip install --no-cache-dir -r requirements.txt
- Production-Only Dependencies
# Install dependencies (faster in Alpine)
RUN npm ci --only=production --silent
Outcome
- Backend image reduced from 7.97GB → 1.78GB (78% reduction!) ✅
- Pre-downloading the Transformer model in the builder stage ensures the model is cached in the virtual environment, avoiding repeated downloads and bloated image layers.
Results Comparison


| Metric | Before | After | Improvement |
|---|---|---|---|
| Backend Image Size | 7.97GB | 1.78GB | 78% reduction |
| Build Time | ~15 minutes | ~8 minutes | 47% faster |
| Security | High risk | Minimal | Much safer |
| Deployment | Slow | Fast | 67% faster pulls |
Key Takeaways
- Multi-stage builds separate build tools from runtime.
- Alpine / slim base images save hundreds of MBs.
- Pre-download large models in the builder stage to cache artifacts.
- Virtual environment copy avoids installing dev dependencies in production.
- Smart package choices (CPU-only PyTorch, lightweight Transformer models) reduce bloat.
Conclusion
Multi-stage Docker builds, combined with pre-downloaded Transformer models and careful dependency selection, drastically reduce image size, improve build and deployment times, and enhance security.
Both my frontend and backend benefited:
- Faster CI/CD pipelines
- Smaller, leaner images
- Safer production environment
This is a must-have practice for any modern containerized application.