Skip to content

Multi-Stage Builds

In a traditional build, all steps—downloading dependencies, compiling code, and packaging—happen in a single container. This results in large images containing build tools that are not needed in production.

Multi-stage builds help reduce the size of Docker images by separating the build environment from the runtime environment. Instead of carrying unnecessary dependencies in the final image, multi-stage builds ensure that only essential files are included.

How Multi-Stage Builds Work

Multi-stage builds define multiple FROM instructions in a single Dockerfile. Each FROM starts a new stage, and you can copy artifacts from one stage to another using COPY --from=stage_name.

Consider the following example where the Dockerfile is split into 2 stages. The first stage provides a Java Development Kit (JDK) environment for building the application. This stage is given the name of builder. The second stage is named final. It uses a slimmer eclipse-temurin:21.0.2_13-jre-jammy image, containing just the Java Runtime Environment (JRE) needed to run the application.

dockerfile
# Stage 1: Build Environment
FROM eclipse-temurin:21.0.2_13-jdk-jammy AS builder
WORKDIR /opt/app
COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline
COPY ./src ./src
RUN ./mvnw clean install

# Stage 2: Runtime Environment
FROM eclipse-temurin:21.0.2_13-jre-jammy AS final
WORKDIR /opt/app
EXPOSE 8080
COPY --from=builder /opt/app/target/*.jar /opt/app/*.jar
ENTRYPOINT ["java", "-jar", "/opt/app/*.jar"]

When building this image, only the final stage from the Dockerfile is included in the resulting Docker image. The intermediate stages are discarded after the build.

Multi-stage builds can also be used to build multiple images from a single Dockerfile by specifying the target stage during the build process. For example:

sh
docker build --target builder -t spring-helloworld-builder .
docker build --target final -t spring-helloworld .

Benefits of Multi-Stage Builds

  • Smaller Images: Unnecessary build tools and dependencies are removed from the final image.
  • Improved Security: Only essential runtime components are included, reducing the attack surface.
  • Easier Maintenance: No need for separate Dockerfiles for building and running.