🐳 Building Secure & Lightweight Docker Images with Multi-Stage Builds and Distroless


In the world of DevOps, security, efficiency, and performance are non-negotiable when building Docker containers. Large, bloated images increase build times, consume more bandwidth, and pose security risks.
This post walks you through multi-stage builds and the use of Distroless images—two powerful strategies to ship production-grade containers that are tiny, fast, and secure.
🚫 The Problem with Traditional Docker Images
Most base images like ubuntu
, debian
, or even openjdk
come with:
Bash or shell
System utilities
Package managers like
apt
While convenient, these additions can:
📦 Bloat image size (200MB+)
🐛 Add unused software and dependencies
🔓 Increase attack surface (e.g., shell access in prod)
Why Does This Matter?
A smaller image:
🏃♂️ Deploys faster
🔐 Is more secure
☁️ Saves bandwidth and storage
📦 Reduces supply chain vulnerabilities
✅ What Are Distroless Images?
Distroless images (from Google) are base images that include only your app and its runtime dependencies, nothing else.
🔐 Benefits:
No shell, SSH, or package manager
Image sizes can be as small as ~2–10MB
Harder for attackers to exploit
Ideal for production environments
⚠️ You can’t exec into a Distroless container (
docker exec -it container bash
) — it doesn’t even have a shell. Debug locally or during CI builds instead.
🧱 Enter Multi-Stage Builds
Distroless is great for runtime, but you still need full-featured tools to build your app. That’s where multi-stage builds come in.
You define multiple FROM
blocks in a single Dockerfile:
Stage 1: Build the app (using Maven, Node, etc.)
Stage 2: Run the app (using a lightweight or Distroless base)
📦 Example: Java App Dockerfile (Multi-Stage)
Here’s a production-grade Dockerfile for a Java app using Maven:
# Stage 1: Build stage
FROM maven:3.8.5-openjdk-17-slim AS build
WORKDIR /app
# Copy dependencies first for Docker layer caching
COPY pom.xml mvnw ./
# Then copy all source files
COPY . .
# Build the app and skip tests to speed up build
RUN ./mvnw package -DskipTests
# Stage 2: Runtime stage
FROM openjdk:11-jre-slim
WORKDIR /app
# Copy only the compiled JAR file
COPY --from=build /app/target/*.jar app.jar
# Expose the port your app runs on
EXPOSE 8080
# Start the application
CMD ["java", "-jar", "app.jar"]
Optional Upgrade: Use a Distroless Runtime
Swap the second FROM
line with this for better security:
FROM gcr.io/distroless/java17
Make sure your app doesn't require a shell or file system tools at runtime before switching.
📉 Image Size Tips
Here’s how to make your Docker images as lean as possible:
✅ Combine RUN steps:
RUN apt update && apt install -y curl && rm -rf /var/lib/apt/lists/*
✅ Use .dockerignore
:
Exclude files like .git
, target/
, node_modules/
, and README.md
.
✅ Pin exact versions:
Avoid FROM openjdk:latest
. Always pin image versions to avoid surprises.
✅ Copy only what you need:
Don't copy the entire context if all you need is a JAR file.
🚀 Push Image to Docker Hub
Once built, tag and push your image:
# Tag the image
docker tag oldNAmeOfImage userName/NewName:v1
# Push it
docker push userName/NewName:v1
To see container logs:
docker logs ContainerName
🧠 Quick Recap
Feature | Traditional Image | Distroless + Multi-stage |
Image Size | 200–500MB | ~10–80MB |
Shell & SSH | Yes | No |
Security | Medium | High |
Build Tools Included | Yes | No |
Best for | Dev & Debug | CI/CD & Production |
🎯 Final Thoughts
Multi-stage builds with Distroless base images are a modern best practice for building Docker images. You get the flexibility of full-featured build environments without compromising on security or performance in production.
This approach is ideal for:
🧩 Microservices
☁️ Cloud-native apps
📦 Kubernetes deployments
🔐 Secure CI/CD pipelines
Subscribe to my newsletter
Read articles from Chaitanya Vamsi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
