Spring Boot Dockerfile with Layered Jar (Multi-Stage, JRE 21)
This Spring Boot Dockerfile extracts your executable jar into layers so dependencies, the Spring Boot loader and your own classes become separate image layers. Code-only changes then rebuild and push just the small application layer. The app runs on eclipse-temurin:21-jre-alpine as a non-root user on port 8080.
- CI status
- Build passing
- Image size (uncompressed)
- 227.8 MB
- Build time
- 71 s
- Runtime image
eclipse-temurin:21-jre-alpine- Port
- 8080
- Last built
- 2026-10-15
# syntax=docker/dockerfile:1
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -B -q dependency:go-offline
COPY src ./src
RUN mvn -B -q package -DskipTests \
&& cp target/*.jar app.jar \
&& java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extracted
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
RUN addgroup -S spring && adduser -S spring -G spring
COPY --from=build /app/extracted/dependencies/ ./
COPY --from=build /app/extracted/spring-boot-loader/ ./
COPY --from=build /app/extracted/snapshot-dependencies/ ./
COPY --from=build /app/extracted/application/ ./
USER spring:spring
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"].git
target/
.idea/
*.iml
.env
Dockerfile
.dockerignoreBefore you build
- Use Spring Boot 3.3 or newer, which provides the jarmode tools used for extraction.
- Make spring-boot-maven-plugin produce one executable jar in target/.
- Run your tests in CI before building the image, because the Docker build skips them.
How this Dockerfile works
FROM maven:3.9-eclipse-temurin-21 AS buildMaven and a full JDK 21 compile and package the app. This stage is discarded once the layers are extracted.
COPY pom.xml . RUN mvn -B -q dependency:go-offlineResolving dependencies from pom.xml alone caches the slow download step until the dependency list changes.
&& java -Djarmode=tools -jar app.jar extract --layers --launcher --destination extractedSpring Boot's jar tools split the fat jar into dependencies, spring-boot-loader, snapshot-dependencies and application directories.
FROM eclipse-temurin:21-jre-alpineThe runtime needs only a Java runtime. The Alpine JRE image is much smaller than the JDK build image.
COPY --from=build /app/extracted/dependencies/ ./Third-party libraries change rarely, so copying them first gives a large layer that stays cached across most rebuilds.
COPY --from=build /app/extracted/application/ ./Your own classes and resources are copied last. A typical code change only rebuilds and pushes this small layer.
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]Starts the app from the extracted layout with Spring Boot's launcher class, instead of running java -jar on a fat jar.
Build and run
docker build -t spring-boot-app .
docker run --rm -p 8080:8080 spring-boot-appSet profiles and secrets at runtime, for example with -e SPRING_PROFILES_ACTIVE=prod. Rebuild after a small code change and watch Docker reuse the cached dependency layers.
Other Spring Boot Dockerfile variants
The plain fat-jar variant is shorter and easier to read. The layered variant pays off for larger apps that are rebuilt and pushed often, because unchanged dependency layers are reused by the registry and every node that pulls the image.
| Variant | Runtime image | Image size | CI |
|---|---|---|---|
| Layered jar (Maven + JRE 21) | eclipse-temurin:21-jre-alpine | 227.8 MB | Passing |
| Maven build + Temurin 21 JRE Alpine | eclipse-temurin:21-jre-alpine | 227.6 MB | Passing |
Troubleshooting
- Unsupported jarmode 'tools'
- Your Spring Boot version is older than 3.3. Upgrade Spring Boot, or use the older layertools jarmode with its extract command instead.
- Error: Could not find or load main class org.springframework.boot.loader.launch.JarLauncher
- The launcher class moved to a new package in recent Spring Boot versions. On older versions use org.springframework.boot.loader.JarLauncher in the ENTRYPOINT instead.
- cp: target/*.jar matches several files
- Your build produces more than one jar, for example a sources jar. Set finalName in pom.xml or copy the exact artifact name instead.
FAQ
- What do layered jars actually save?
- When only your code changes, Docker rebuilds and pushes the small application layer, while the large dependency layers are reused. That speeds up CI builds and deployments.
- Is the snapshot-dependencies layer needed?
- It holds SNAPSHOT versions, which change more often than releases. Keeping them separate stops snapshots from invalidating the stable dependencies layer.
- Can I combine this with a native image?
- No. Native images compile the app into a single executable without a JVM, so there are no jar layers to extract. That approach needs a different Dockerfile.