Dockerfile & Multi-stage Builds

RoadmapsPython Backend

Scenario

Your Python Docker image is 1.5GB. It takes 10 minutes to deploy, and a security scanner flags that you shipped a C++ compiler and pip caching artifacts into production.

Mental model

A multi-stage Dockerfile is a factory with two rooms. Room 1 (builder) has all the heavy tools needed to compile dependencies. Room 2 (runtime) is completely empty; you only copy the finished product from Room 1. Room 2 gets shipped.

Deep dive

Docker containers package an application and its dependencies into a single runnable artifact.

A standard Python `Dockerfile` often includes build tools (like `gcc`) required to compile certain packages. If you ship this image, it's bloated and has a large attack surface.

Multi-stage builds solve this. You use a `builder` stage to install dependencies into a virtual environment, then use a slim `runtime` stage where you simply copy the virtual environment over.

Code examples

Single-stage (Bloated)

FROM python:3.11
WORKDIR /app
COPY requirements.txt .
# Caches pip artifacts permanently in the layer
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]

This image includes the entire Debian OS, development headers, and `pip` caches. It's huge.

Multi-stage Build (Ground Truth)

# Stage 1: Builder
FROM python:3.11-slim AS builder
RUN apt-get update && apt-get install -y gcc
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Stage 2: Runtime
FROM python:3.11-slim
# Copy only the compiled environment, leave gcc behind
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /app
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

The final image drops the `builder` stage completely. It only contains the application code and the clean virtual environment.

Common mistakes

Recall questions

Questions & answers

Why do we create a Python virtual environment (`venv`) inside a Docker container when the container itself is already isolated?

While isolation isn't the issue, a virtual environment makes it extremely easy to move dependencies in a multi-stage build. You can simply `COPY --from=builder /opt/venv /opt/venv` and you've transferred exactly what you need without copying system-level files.

What is the order of operations in a Dockerfile for optimal caching?

Copy `requirements.txt`, run `pip install`, and THEN copy the application code. This ensures that if only application code changes, the dependency installation layer is cached.

Return to Python Backend Roadmap