2 min read

Reducing Python Docker Image Size with Multi-Stage Builds

One of the most effective ways to reduce the size of a Docker image is using multi-stage builds. This technique allows you to use a heavy image with all the necessary build tools (compilers, headers) to install dependencies, and then copy only the necessary artifacts (installed packages) to a lightweight "runner" image.

The Problem

When you install Python packages (like numpy, pandas, or psycopg2), pip often needs to compile C code. This requires system tools like gcc and python3-dev.

If you install these tools in your final image, it becomes unnecessarily large and increases the security attack surface.

The Solution: Multi-Stage Dockerfile

Here is a pattern that separates the build environment from the runtime environment.

# __________ STAGE 1: BUILDER __________
# We use a slim image, but we will install build tools here.
FROM python:3.11-slim as builder

WORKDIR /app

# 1. Install system dependencies required for building Python packages
# (e.g., gcc, libpq-dev). These won't make it to the final image.
RUN apt-get update && apt-get install -y \
    build-essential \
    && rm -rf /var/lib/apt/lists/*

# 2. Create a virtual environment to isolate our packages
RUN python -m venv /opt/venv

# 3. Activate the venv for the following commands
ENV PATH="/opt/venv/bin:$PATH"

# 4. Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt


# __________ STAGE 2: FINAL __________
# We start fresh with a clean, lightweight image.
FROM python:3.11-slim

WORKDIR /app

# 1. Copy the virtual environment from the 'builder' stage
# This is the magic step. We get the compiled packages without the compiler tools.
COPY --from=builder /opt/venv /opt/venv

# 2. Activate the venv
ENV PATH="/opt/venv/bin:$PATH"

# 3. Copy application code
COPY . .

# 4. Run
CMD ["python", "main.py"]

Key Concepts

  1. as builder: Names the first stage so we can reference it later.
  2. Virtual Environment (/opt/venv): We install everything into a specific folder. This makes it incredibly easy to copy just the dependencies to the next stage.
  3. COPY --from=builder: Copies files from the previous stage instead of the host machine.
  4. Result: Your final image contains only Python, your code, and the compiled libraries. It does not contain gcc, apt caches, or build artifacts.

programming/python/docker-python