Optimizing Docker Images: Multi-Stage Builds and Distroless Containers
Humphrey Ikhalea
Cloud-Native DevOps & Backend Engineer (Kubernetes, AWS, Azure, GCP, Ansible, Terraform, Gitlab, Docker, Argo CD, Jenkin, Python, NodeJs, Golang, Bash)
Understanding the Problem
Docker image size and security have long been pain points for developers and operations teams. Traditional Docker images, often built on rich base images like Ubuntu, tend to include unnecessary packages and dependencies. This results in bloated image sizes and increased security risks due to a larger attack surface.
The Solution: Multi-Stage Docker Builds
Multi-stage Docker builds provide an available solution to these challenges. By breaking the build process into multiple stages, developers can create leaner, more efficient Docker images while adhering to security best practices.
Let’s dive into how multi-stage Docker builds work with practical examples for Python, Golang, and Java.
Python Example: Flask Application
Dockerfile: Multi-Stage Build
# --- Stage 1: Build Stage ---
FROM ubuntu AS build-app
WORKDIR /app
COPY . .
RUN apt-get update && \
apt-get install -y python3 python3-pip && \
pip install --no-cache-dir -r requirements.txt
# --- Stage 2: Final Stage ---
FROM gcr.io/distroless/python3
WORKDIR /app/
# Copy the application code and dependencies from the build stage
COPY --from=build-app /app/ .
COPY --from=build-app /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
# Set the Python path
ENV PYTHONPATH=/usr/local/lib/python3.11/site-packages
# Expose port 5000 for the Flask application
EXPOSE 5000
# Define the default command to run the application
CMD ["app.py"]
Golang Example: Simple Web Server
Dockerfile: Multi-Stage Build
# --- Stage 1: Build Stage ---
FROM golang:1.20 AS build-app
WORKDIR /app
COPY . .
RUN go build -o myapp .
# --- Stage 2: Final Stage ---
FROM gcr.io/distroless/base
WORKDIR /app/
# Copy the binary from the build stage
COPY --from=build-app /app/myapp .
# Expose port 8080 for the web server
EXPOSE 8080
# Define the default command to run the application
CMD ["./myapp"]
Java Example: Spring Boot Application
Dockerfile: Multi-Stage Build
# --- Stage 1: Build Stage ---
FROM maven:3.8.6 AS build-app
WORKDIR /app
COPY . .
RUN mvn clean package -DskipTests
# --- Stage 2: Final Stage ---
FROM gcr.io/distroless/java17
WORKDIR /app/
# Copy the JAR file from the build stage
COPY --from=build-app /app/target/myapp.jar .
# Expose port 8080 for the Spring Boot application
EXPOSE 8080
# Define the default command to run the application
CMD ["myapp.jar"]
Breaking Down the Dockerfiles
Python:
Golang:
Java:
领英推荐
Benefits of Multi-Stage Builds
By separating the build and runtime environments, multi-stage builds eliminate unnecessary dependencies, resulting in smaller images.
Using distroless images minimizes the attack surface, reducing the risk of vulnerabilities.
Simplifies the Docker image creation process, making it easier to produce optimized images.
Exploring Distroless Container Images
Distroless container images take minimalism to the next level. These images strip away all non-essential components, leaving only the runtime environment required to execute the application.
Key Features of Distroless Images
Contains only the bare essentials, reducing the risk of security threats.
Fewer packages and dependencies result in smaller image sizes, faster pulls, and quicker deployments.
Available for various programming languages, including Python, Java, and Go, providing tailored runtime environments.
Why Use Distroless Images?
Conclusion
By leveraging multi-stage Docker builds and distroless container images, developers can create smaller, more secure, and efficient Docker images. These practices not only streamline the build process but also enhance the overall security and performance of containerized applications.
If you found this article helpful, subscribe, share it with your peers! Let’s spread the knowledge and build better, more secure applications together. ??