A Dockerfile builder for Spring Boot that produces a clean multi-stage Dockerfile for Java microservices. You choose Maven or Gradle, a JDK, a port, and whether to wire in an Actuator health check, and it emits a build that compiles your jar in one image and runs it in a slim JRE image as a non-root user.
How it works
The build stage uses a full image that contains both the JDK and your build tool. For
Maven it runs mvn dependency:go-offline after copying only pom.xml, so dependency
resolution is cached as its own layer, then runs mvn package -DskipTests. For Gradle it
resolves dependencies, then runs bootJar. Either way the resulting fat jar is copied to a
predictable path, app.jar.
The runtime stage starts from eclipse-temurin:<jdk>-jre-alpine — a slim image with
only the Java runtime. It creates a dedicated spring user, copies the jar with the right
ownership, switches to that user with USER, and starts the app with MaxRAMPercentage so
the JVM sizes its heap against the container limit. When enabled, the HEALTHCHECK polls
/actuator/health and looks for the UP status.
Tips and notes
- Tests are skipped in the image build (
-DskipTests/-x test) on purpose — run your test suite in CI before building the image, not inside the container build. - For even faster startup and smaller images you can extract the Spring Boot layered
jar with
java -Djarmode=layertools -jar app.jar extract; this builder keeps the simpler single-jar form, which is correct for the vast majority of services. - Always pin an LTS JDK (21, 17, or 11) so your runtime matches what you tested against.
- Make sure
spring-boot-starter-actuatoris on the classpath andmanagement.endpoint.health.probes.enabled=trueis set before relying on the health check.
What the generated Dockerfile looks like
A Maven build targeting JDK 21 produces something like:
# Build stage
FROM eclipse-temurin:21-jdk AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src ./src
RUN mvn package -DskipTests -q
# Runtime stage
FROM eclipse-temurin:21-jre-alpine
RUN addgroup -S spring && adduser -S spring -G spring
WORKDIR /app
COPY --from=builder --chown=spring:spring /build/target/*.jar app.jar
USER spring
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s \
CMD wget -qO- http://localhost:8080/actuator/health | grep UP || exit 1
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
The dependency-caching pattern is the single biggest time-saver in repeated builds. Because pom.xml is copied and dependencies resolved before any source is touched, Docker caches that layer. As long as pom.xml does not change, a code-only rebuild skips the slow download step entirely.
Why JRE instead of JDK at runtime
The full JDK includes the compiler (javac), debugging tools, jshell, and documentation — none of which a deployed application needs. The JRE contains only what the JVM needs to execute .class files. Using the JRE image in the runtime stage produces a meaningfully smaller final image and removes compiler tools that could be used by an attacker who gained container access.
eclipse-temurin is the open-source distribution of Eclipse Adoptium builds (formerly AdoptOpenJDK), the standard community choice for containerised Java. The -alpine suffix adds an even smaller base OS layer at the cost of using musl libc, which works well for most JVM workloads.
Health check configuration
When Actuator health checking is enabled, the container starts with a 60-second grace period (--start-period=60s) to allow Spring Boot to complete its initialisation before health checks begin. Spring Boot typically takes 10–30 seconds depending on the application size, so the 60-second window gives comfortable headroom. In Kubernetes, use separate readinessProbe and livenessProbe entries in your Pod spec rather than the Docker HEALTHCHECK instruction, since Kubernetes has its own health mechanism and does not use the Docker one.