Skip to main content

Command Palette

Search for a command to run...

🐳 Docker – Day 4 : Multi-Stage & Distroless Image

Building Secure, Lightweight, Production-Ready Images

Published
4 min read
🐳 Docker – Day 4 : Multi-Stage & Distroless Image
A

Cloud & DevOps enthusiast learning in public ☁️⚙️ Documenting my journey through systems, automation, and real-world engineering problems. Focused on fundamentals, practical learning, and continuous growth.

In production, building lightweight, secure, and efficient containers is crucial. Today, we’ll explore multi-stage builds and distroless images, focusing on Python applications, and we’ll also run a simple demo to see it in action.


🏗️ What is a Multi-Stage Docker Build?

A multi-stage build allows using multiple FROM statements in a single Dockerfile.

Each stage:

  • Has its own base image

  • Performs specific tasks (build, test, runtime)

  • Passes only the required artifacts to the next stage

The final image contains only what is needed to run the application, not build tools or temporary files.

📷 Multi-stage build

Understanding Multi-Stage Docker Builds | Blacksmith


⚡ Why Multi-Stage Builds Are Important

Before multi-stage builds:

  • Build tools like pip or compilers were included in final images

  • Images were large, slow, and insecure

With multi-stage builds:

  • Smaller images → faster downloads and startup

  • Lower attack surface → better security

  • Separation of build vs runtime

  • Efficient CI/CD pipelines


📝 How Multi-Stage Builds Work (Python Example)

Step 1 – Build Stage:

  • Install Python dependencies from requirements.txt

  • Copy the application code

  • Prepare everything needed for runtime

Step 2 – Runtime Stage (Distroless):

  • Only include Python interpreter, dependencies, and app code

  • No pip, shell, or extra OS tools

  • Run the application securely


🧱 What is a Distroless Image?

A distroless image contains only:

  • Python runtime

  • Application dependencies

It does not include:
❌ Shell
❌ Package managers
❌ OS utilities

Distroless images are designed only to run applications, not manage them.


🛠️ Debugging Distroless Containers

⚠️ Distroless images do not have a shell, so docker exec -it container /bin/sh will fail.

✅ Debugging approaches:

  • Use debug images

  • Temporarily switch to Alpine

  • Use sidecar containers

  • Enable detailed logs

📷 Image idea: Debugging workflow for distroless containers


📊 Distroless vs Slim vs Alpine (Python)

FeatureDistrolessAlpineSlim
Shell
Security⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
DebuggingHardEasyEasy
Image SizeSmallestSmallMedium
Production Use⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

✅ Best Practices (Python Industry Standards)

  • Always use multi-stage builds

  • Use distroless images in production

  • Avoid debugging inside production images

  • Separate build and runtime concerns

  • Run containers as non-root users

  • Scan images for vulnerabilities


🐍 Step-by-Step Python Example

We’ll build a minimal Python app that prints a message inside a multi-stage, distroless container.

1️⃣ Python Application Code (app.py)

# app.py
def main():
    print("🐳 Hello from a Python container!")
    print("This container uses multi-stage build and distroless image.")

if __name__ == "__main__":
    main()

What it does:

  • Prints a friendly message to show the container is running

  • Demonstrates Python app running in distroless container


2️⃣ Requirements File (requirements.txt)

# No external dependencies for this demo

Included to show the multi-stage pattern for dependency installation


3️⃣ Multi-Stage + Distroless Dockerfile (Dockerfile)

# ---------- Build Stage ----------
FROM python:3.11-slim AS builder
WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt --target /deps

# Copy application code
COPY app.py .

# ---------- Runtime Stage ----------
FROM gcr.io/distroless/python3
WORKDIR /app

# Copy dependencies and app from builder
COPY --from=builder /deps /deps
COPY --from=builder /app /app

# Set Python path
ENV PYTHONPATH=/deps

# Run app
CMD ["app.py"]

Explanation:

  • Build stage installs dependencies and prepares code

  • Runtime stage is distroless, containing only Python runtime and app

  • Final image is small, secure, and production-ready


4️⃣ Build and Run the Container

Step 1 – Build the Docker image

docker build -t python-demo:latest .

Step 2 – Run the container

docker run --rm python-demo:latest

Expected Output:

🐳 Hello from a Python container!
This container uses multi-stage build and distroless image.

Step 3 – Verify running containers

docker ps

Step 4 – Stop containers (if needed)

docker stop <container_id>

The output confirms the Python app runs successfully inside a multi-stage, distroless container.


🛠️ Debugging Distroless Python Containers

⚠️ Distroless images do not have a shell, so commands like:

docker exec -it container /bin/sh

❌ will fail.

✅ Key points:

  • You cannot enter the container to change the code.

  • Any change in the application requires:

    1. Updating the source code locally

    2. Rebuilding the Docker image

    3. Running a new container from the updated image

Debugging approaches:

  • Use debug images with shells (e.g., Alpine or slim) temporarily

  • Use sidecar containers

  • Enable detailed application logs

This design enforces immutable infrastructure—containers are read-only at runtime, improving security and consistency in production.

Docker Simplified: A Beginner's Guide

Part 4 of 10

A beginner-friendly Docker series covering core concepts, architecture, hands-on examples, Dockerfiles, images, containers, and real-world usage — explained in simple terms.

Up next

🐳 Docker Storage - Day 5 : Complete Notes (Bind Mounts, Volumes, Named Volumes, tmpfs)

Understanding Persistent and Ephemeral Storage in Docker