🐳 Docker – Day 4 : Multi-Stage & Distroless Image
Building Secure, Lightweight, Production-Ready Images

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

⚡ 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.txtCopy 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)
| Feature | Distroless | Alpine | Slim |
| Shell | ❌ | ✅ | ✅ |
| Security | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ |
| Debugging | Hard | Easy | Easy |
| Image Size | Smallest | Small | Medium |
| 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:
Updating the source code locally
Rebuilding the Docker image
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.




