Developer Guides

Dockerfile Best Practices: Multi-Stage Builds Explained

Learn the best practices for writing Dockerfiles with a focus on multi-stage builds to optimize performance, size, and security.

5 min read

Learn how to optimize your Dockerfiles using multi-stage builds, advanced BuildKit features, and Dockerfile best practices to create smaller, faster, and more secure Docker images. This guide provides step-by-step instructions and actionable tips for efficient container image creation.

Introduction to Dockerfile Best Practices

Dockerfiles act as blueprints for building Docker images. They specify the necessary instructions to set up and configure environments securely and efficiently. One transformational feature is multi-stage builds, which separate development and runtime stages to produce minimal and production-ready images. This approach helps reduce image bloat, speeds up builds, and enhances overall security.


Prerequisites for Using BuildKit

Before diving into multi-stage builds and BuildKit-specific optimizations, ensure that your environment is properly set up.

prerequisites

  • Docker version: 18.09 or later.
  • Enable BuildKit:
    • Via environment variable: DOCKER_BUILDKIT=1.
    • Or by configuring the Docker daemon: Add "features": { "buildkit": true } to /etc/docker/daemon.json.
  • Linux environment recommended (BuildKit support for Windows is partial as of this writing).

Steps to Write an Optimal Dockerfile

To create maintainable, efficient, and secure Dockerfiles, follow these steps:

IT_GUIDES_COMPONENT_1

Creating Minimal Docker Images


Using Multi-Stage Builds

One of the most effective ways to create optimized and secure images is to use multi-stage builds. Here's how they work:

  1. Define separate stages: Start each stage with a new FROM instruction. For instance, define a build stage to compile the code and a final stage to run the application.

    dockerfile
    # Build stage
    FROM maven:3.8.5-jdk-11 AS build
    WORKDIR /app
    COPY pom.xml .
    RUN mvn dependency:go-offline
    COPY src/ ./src
    RUN mvn package
    
    # Final runtime stage
    FROM openjdk:11-jre-slim
    WORKDIR /app
    COPY --from=build /app/target/app.jar app.jar
    CMD ["java", "-jar", "app.jar"]
  2. Use targeted builds: Specify the stage to build with --target:

bash
   docker build --target build -t my-app-build .
   docker build --target final -t my-app .
  1. Benefits:
    • Separate concerns by keeping build dependencies and runtime dependencies apart.
    • Reduce image size significantly by excluding build tools and intermediate stages from the final image.

Advanced BuildKit Features

BuildKit introduces advanced features like cache and secret mounts to improve build efficiency and security.

steps

  1. Use cache mounts: Prevent redundant downloads during builds by caching state between builds.

    dockerfile
    RUN --mount=type=cache,target=/root/.cache/pip \
        pip install -r requirements.txt
  2. Utilize secret mounts: Share secrets securely without embedding them in your Dockerfile.

    dockerfile
    RUN --mount=type=secret,id=my_secret \
        ./fetch_private_repo.sh

    Run the build with the --secret flag:

bash
   docker build --secret id=my_secret,src=/path/to/secret .
  1. Enable SSH agent forwarding: Access private Git repositories using your SSH agent.

    dockerfile
    RUN --mount=type=ssh \
        git clone git@github.com:user/private-repo.git

    Enable SSH during the build:

bash
   docker build --ssh default .

Verification and Common Mistakes


Summary of Dockerfile Optimization


FAQ

What are multi-stage builds in Docker?

Multi-stage builds allow you to use multiple FROM statements in a Dockerfile to create distinct build and final stages. This approach enables the creation of smaller and more secure production images by keeping build dependencies and tools out of the final image.

Why should I enable BuildKit?

BuildKit provides significant improvements over the legacy Docker builder, including faster builds, better caching mechanisms, support for secure secret mounts, and more efficient use of resources. It is recommended for optimized Dockerfile development.

How do I reduce the size of a Docker image?

To reduce the size of a Docker image, use a minimal base image (e.g., alpine), avoid unnecessary package installations, clear cache files, and leverage multi-stage builds to remove build tools from the final image.

How can I pass secrets securely into a Docker build?

Use BuildKit’s secret mount feature to pass secrets securely during the build process. Add --mount=type=secret,id=your_secret to a RUN step in the Dockerfile and provide the secret with docker build --secret.


Official reference: Dockerfile reference.