Command Palette

Search for a command to run...

Hectal
PHASE 6Intermediate ~14 min· topic 1 of 4

Topic 6.1

Multi-Stage Builds for Maven/Gradle

In one line

Compile your app INSIDE Docker (so nobody needs Maven/Java installed locally to build it) while keeping the final shipped image free of the entire build toolchain.

0/4 · 0%

Think of it like this

A professional kitchen's prep area versus its plating counter. All the messy work — chopping, mixing bowls, flour everywhere — happens in the prep area (the BUILD stage). Only the finished, plated dish (the compiled JAR) makes it to the customer-facing counter (the FINAL image) — the customer never sees or pays for the mess of the kitchen equipment itself.

Key ideas

  1. 01

    A MULTI-STAGE build has multiple FROM instructions in ONE Dockerfile, each starting a new, independent stage. Later stages can selectively COPY --from=<earlier-stage> specific files out of an earlier stage — everything else from that earlier stage (the compiler, cached build files, source code) is simply LEFT BEHIND and never becomes part of the final image.

  2. 02

    The typical two-stage Java pattern: STAGE 1 uses a maven (or gradle) image containing a full JDK and build tool, runs mvn package to produce a JAR. STAGE 2 starts FRESH from a minimal JRE-only base image and COPY --from=build just the resulting JAR — the final image contains ZERO trace of Maven, the JDK compiler, or your raw source code.

  3. 03

    This solves two real problems at once: (1) reproducible builds — anyone with Docker installed can build your app identically, without needing a specific local Maven/JDK version installed at all; (2) final image size and attack surface — a build toolchain can easily be hundreds of MB, none of which your running app actually needs.

  4. 04

    The build-stage layers still benefit from the SAME caching principles as Topic 2.2 — copy pom.xml and run mvn dependency:go-offline before copying source code, exactly as before, just now happening as one stage within a larger multi-stage file.

  5. 05

    You can have MORE than two stages — e.g. a separate stage for running tests that the final image never includes at all, letting docker build itself double as your CI's test-runner step if you want that.

In your stack

  • →

    This IS essentially the standard, idiomatic way any serious Java team containerizes a Spring Boot app today — a single Dockerfile that both builds AND packages the app, with no separate 'first run mvn package locally, then docker build' two-step process required at all.

Code & diagrams

Dockerfilemarkdown

Two stages: a full JDK+Maven builder, and a minimal JRE-only final image.

# ---- STAGE 1: build ----
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /app

# Dependency layer first — cached across builds unless pom.xml changes (Topic 2.2)
COPY pom.xml .
RUN mvn dependency:go-offline

# Source code layer — changes every commit, cheap to rebuild on top of cached deps
COPY src ./src
RUN mvn package -DskipTests

# ---- STAGE 2: final runtime image ----
FROM eclipse-temurin:17-jre
WORKDIR /app

# Pull ONLY the built jar out of the build stage — nothing else comes along
COPY --from=build /app/target/*.jar app.jar

RUN useradd -m -u 1000 appuser
USER appuser

EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
MultiStageBuilddiagram

Only the jar crosses the bridge between stages — everything else in Stage 1 is discarded.

Rendering diagram…

Explain it without notes

01

Why does the FINAL image in a multi-stage build contain zero trace of Maven, even though Maven was genuinely used to build the app?

02

A teammate says 'we don't need multi-stage builds, we just run mvn package on our laptop first, then Docker just copies the jar in.' What real problem does that workflow have that multi-stage solves?

Practice

01

Convert a Dockerfile that assumes a pre-built jar (Topic 2.1's version) into a proper multi-stage build that compiles from source.

02

Build your multi-stage Dockerfile and run docker images to compare the FINAL image's size against the size of the intermediate maven:3.9-eclipse-temurin-17 build-stage image alone (docker images | grep maven).

Trade-offs

  • ↔

    Multi-stage builds mean Docker itself now downloads and caches build-tool images (Maven, etc.) locally, using disk space for the BUILD stage's layers even though they never ship — a reasonable, standard trade for reproducible builds and a lean final image; that disk usage can be reclaimed anytime with docker system prune.

Done when you can

  • I can write a two-stage Dockerfile that builds AND packages a Java app from source.

  • I've personally compared the final image size against the build-stage image size.

  • I understand exactly why only explicitly-copied files cross between stages.