exit lab
Hands-on labs
Intermediate

Dockerize a Spring Boot App

Package a Spring Boot service into a small, production-ready image using a multi-stage build.

45 min

Prerequisites

  • Docker installed and running
  • A Spring Boot project with a Maven pom.xml
  • Basic familiarity with the terminal

Architecture

Build stage vs. runtime stage

Source + pom.xmlbuild stage
Maven buildmvn package
app.jarartifact only
JRE imageruntime stage

Build checklist

Steps

0 of 4 complete

Step 1

Write a multi-stage Dockerfile

Stage one compiles the JAR with Maven; stage two copies only the built artifact into a slim JRE image — the final image never contains Maven, the JDK, or your source code.

dockerfile
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S app && adduser -S app -G app
COPY --from=build --chown=app:app /app/target/*.jar app.jar
USER app
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Step 2

Build the image

bash
docker build -t myapp:1.0 .
Step 3

Run it locally

bash
docker run -p 8080:8080 -e SPRING_PROFILES_ACTIVE=prod myapp:1.0
Step 4

Confirm it's actually responding

bash
curl http://localhost:8080/actuator/health

Expected result

What success looks like

A running container serving the app on port 8080, and an image significantly smaller than a single-stage build would produce — because the final layer never carries Maven or the JDK, only a JRE and one JAR file.

Lessons learned

Tip

COPY pom.xml first, then run dependency:go-offline, before copying src/ — Docker caches layers, so dependency resolution only re-runs when pom.xml actually changes, not on every source edit.

Tip

-DskipTests in the Dockerfile is deliberate: tests should run in CI before the image is even built, not slow down every local image build.

Tip

The runtime creates and switches to an unprivileged app user; a container intended for production should not run the JVM as root.

Tip

eclipse-temurin:21-jre-alpine is readable but mutable. Resolve and pin a reviewed digest for controlled production builds, then use automation to propose digest updates.