Building Efficient Docker Images with Multistage Builds and Distroless Images

Building Efficient Docker Images with Multistage Builds and Distroless Images

In the world of containerization, efficiency and security are paramount. Two powerful techniques that help achieve these goals are Multistage Docker Builds and Distroless Images. This blog will explore these concepts in detail, demonstrating how they can be used to build lightweight, secure, and efficient Docker images.

What is a Multistage Docker Build?

A multistage Docker build is a feature in Docker that allows the use of multiple FROM statements in a Dockerfile. This approach helps create smaller and more efficient images by enabling intermediate images in the build process, which are eventually discarded. This is particularly useful for reducing the size of the final Docker image, as it allows copying only the necessary artifacts from intermediate stages.

Key Benefits of Multistage Docker Builds:

  1. Reduced Image Size: By only including the final artifacts and not the build tools and dependencies, the size of the final image is minimized.

  2. Improved Security: Smaller images have fewer components, reducing the attack surface.

  3. Enhanced Efficiency: Each stage can be cached independently, potentially speeding up builds if intermediate stages haven't changed.

# Stage 1: Build Stage
FROM golang:1.16 as builder

# Set the working directory inside the container
WORKDIR /app

# Copy the source code into the container
COPY . .

# Build the Go app
RUN go build -o myapp

# Stage 2: Final Stage
FROM alpine:latest

# Copy the compiled binary from the builder stage
COPY --from=builder /app/myapp /usr/local/bin/myapp

# Specify the command to run the app
CMD ["myapp"]

What is a Distroless Container Image?

A distroless container image is a minimal Docker image that contains only the application and its runtime dependencies, without including a full-fledged operating system or unnecessary tools and libraries. This approach results in smaller, more secure images that reduce the attack surface and potential vulnerabilities.

Key Features and Benefits of Distroless Images:

  • Minimal Size: Distroless images are significantly smaller than traditional base images, leading to faster pulling and deployment times.

  • Enhanced Security: With fewer components, fewer potential vulnerabilities and attack vectors exist. There are no package managers, shells, or other extraneous files.

  • Simplified Maintenance: Since there are fewer dependencies, there are fewer components to manage and update.

  • Improved Performance: Smaller images can lead to better performance due to reduced overhead.

  • Consistency and Predictability: By only including what is necessary to run the application, distroless images ensure a consistent and predictable runtime environment.

Combining Multistage Builds with Distroless Images

Combining multistage builds with distroless images is a powerful technique for creating efficient and secure Docker images. Let's break down the process step-by-step to understand how these techniques work together to produce a minimal, production-ready container.

Step-by-Step Breakdown

Creating an EC2 Instance:

  • Instance Type: Choose t2.micro (eligible for free tier) to minimize costs.

  • Operating System: Use Ubuntu for compatibility with most Docker tools and commands.

  • Installation of Docker: Install Docker on the EC2 instance to build and run Docker images.

sudo apt-get update
sudo apt-get install -y docker.io
sudo systemctl start docker
sudo systemctl enable docker

Cloning the Repository:

Clone a GitHub repository containing a sample Golang application.

git clone https://github.com/nishankkoul/Golang-Multistage-Docker-Build.git

Create a Dockerfile Without Multistage:

Navigate to the dockerfile-without-multistage folder and create a Dockerfile:

cd dockerfile-without-multistage
vim Dockerfile
FROM ubuntu AS build
RUN apt-get update && apt-get install -y golang-go
ENV GO111MODULE=off
COPY . .
RUN CGO_ENABLED=0 go build -o /app .
ENTRYPOINT ["/app"]

Build the Docker image:

docker build -t go-without-multistage .

Check the Image Size:

After building the Docker image, you'll notice that its size is around 645 MB, which is quite large.

Create a Dockerfile with Multistage and Distroless:

Navigate back to the root directory and create a new Dockerfile:

cd ..
vim Dockerfile
# Stage 1: Build Stage
FROM ubuntu AS build
RUN apt-get update && apt-get install -y golang-go
ENV GO111MODULE=off
COPY . .
RUN CGO_ENABLED=0 go build -o /app .

# Stage 2: Final Stage
FROM scratch
COPY --from=build /app /app
ENTRYPOINT ["/app"]

Build the new Docker image:

docker build -t go-with-multistage .

Compare the Image Sizes:

After building the multistage Docker image, you'll see a significant size reduction. The new image size is just 1.96 MB. This drastic decrease showcases the power of using multistage builds and distroless images.

Advantages of Distroless Images

  • Smaller Image Size: Distroless images include only the application and its runtime dependencies, resulting in significantly smaller images.

  • Enhanced Security: With fewer components, the attack surface is minimized, and there are fewer potential vulnerabilities.

  • Simplified Maintenance: Fewer components to manage and update.

  • Improved Performance: Reduced size and minimized bloat lead to better performance.

  • Consistency and Predictability: Ensures a consistent runtime environment by including only the necessary components.

Conclusion

Combining Multistage Builds with Distroless Images is a powerful approach to creating minimal, secure, and efficient Docker containers. By reducing image size and minimizing the attack surface, this method enhances both security and performance, making it ideal for production environments.

For more distroless images for different languages, refer to the Distroless GitHub repository.