Optimizing Docker Images with Multi-Stage Builds (Day-21)

Rohit DeoreRohit Deore
4 min read

Introduction

Docker has become an integral part of the DevOps toolkit, streamlining the deployment process and ensuring consistency across different environments. However, as Docker images grow in complexity, their sizes can become a concern, impacting deployment times and storage requirements. In this blog post, we'll explore Multi-Stage Docker Builds as a powerful technique to address these issues and improve both performance and security.

1. Multi-Stage Docker Builds

Purpose:

Multi-Stage Docker Builds allow you to create more efficient and smaller Docker images by using multiple build stages within a single Dockerfile. Each stage represents a phase in the build process, and only the necessary artifacts are carried over to the final image. This reduces the overall size of the image and ensures that only essential components are included.

Benefits:

  • Reduced Image Size: The primary advantage is a significantly reduced image size, as unnecessary build dependencies and intermediate artifacts are excluded.

  • Enhanced Build Speed: By isolating build dependencies in an earlier stage, subsequent stages only focus on essential components, leading to faster build times.

  • Isolation of Build and Runtime Environments: Multi-stage builds help separate the development environment from the runtime environment, ensuring that only the required dependencies and binaries are present in the final image.

Example:

# Stage 1: Build Stage
FROM node:14 as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Stage 2: Production Stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html

In this example, the first stage builds the Node.js application, and the second stage copies only the compiled artifacts into the final Nginx-based image. This separation ensures that the production image only contains what is necessary for runtime, reducing its size significantly.

2. Reduce Docker Image Size

Purpose:

Reducing Docker image size is crucial for faster deployments and lower storage costs. Multi-Stage Builds contribute to this goal, but additional strategies can be employed.

Additional Strategies:

  • Alpine Base Image: Using Alpine Linux as a base image is a common practice due to its lightweight nature. It provides a minimal environment, reducing the overall size of the image.

  • Dependency Cleanup: After installing dependencies, it's crucial to remove unnecessary files and caches within the same RUN command to avoid unnecessary bloat.

Example:

# Base Image
FROM alpine:3.14

# Install Dependencies
RUN apk add --no-cache \
    python3 \
    && pip3 install --upgrade pip \
    && pip3 install gunicorn

# Application Stage
FROM python:3.9-alpine
WORKDIR /app
COPY --from=builder /app /app
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]

In this example, we use the alpine image for the base and install only the necessary dependencies. The application stage then copies the required artifacts from the builder stage, resulting in a more compact final image.

3. Distro-Less Images

Purpose:

Distro-less images take minimalism to the extreme by excluding the operating system distribution entirely. These images only contain the necessary libraries and binaries required to run the application.

Benefits:

  • Reduced Attack Surface: Distro-less images have a minimal footprint, reducing the potential attack surface. This enhances security by limiting the number of components that could be exploited.

  • Simplified Maintenance: With fewer components, distro-less images simplify maintenance and updates, as there is less need to track and patch vulnerabilities in the underlying OS.Example:

# Build Stage
FROM golang:1.17 as builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o myapp

# Distro-less Stage
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /app/myapp /
USER nonroot
ENTRYPOINT ["/myapp"]

Here, the final image is based on gcr.io/distroless/static:nonroot, a distro-less image. This results in a smaller image size and reduces the attack surface, enhancing security.

4. Container Security

Purpose:

Security is paramount when dealing with containers. Multi-Stage Builds inherently improve security by minimizing the attack surface, but additional measures should be taken.

Best Practices:

  • Regular Image Updates: Regularly update base images to incorporate the latest security patches and improvements. Stale images can expose vulnerabilities.

  • Vulnerability Scanning: Utilize tools for vulnerability scanning to identify and address potential security issues in your container images.

  • Principle of Least Privilege: Adopt the principle of least privilege by running containers with the minimum necessary permissions. Avoid running containers as the root user whenever possible.

Example:

# Build Stage
FROM maven:3.8.4-openjdk-11 as builder
WORKDIR /app
COPY . .
RUN mvn clean install

# Production Stage
FROM adoptopenjdk:11.0.11_9-jre-hotspot-bionic
COPY --from=builder /app/target/myapp.jar /app/myapp.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/myapp.jar"]

In example, the Dockerfile uses specific base images for both the build and production stages. Adopting a minimalistic approach and following best practices, such as running the application as a non-root user, contributes to a more secure containerized environment.

In Closing

Multi-Stage Docker Builds are a versatile tool in the DevOps arsenal, enabling developers and operations teams to create more efficient, smaller, and secure Docker images. By implementing these strategies, you can significantly enhance your containerized applications' performance, reduce resource consumption, and bolster overall system security. As you embrace these techniques, remember to stay vigilant with security practices and regularly update your base images to keep your containers robust and resilient.


Keep Exploring...

0
Subscribe to my newsletter

Read articles from Rohit Deore directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rohit Deore
Rohit Deore

Student and Developer