Containers are the standard unit of deployment for Spring Boot services. A naive image works, but a little care produces smaller images that build faster and start quicker.
A multi-stage build
Build the JAR in one stage and copy only the artifact into a slim runtime image. This keeps your Maven cache and toolchain out of the final layer.
# Build stage
FROM eclipse-temurin:21-jdk AS build
WORKDIR /app
COPY . .
RUN ./mvnw -q package -DskipTests
# Runtime stage
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]Layered JARs for better caching
Spring Boot can split the JAR into layers so dependencies (which rarely change) cache separately from your code (which changes constantly).
FROM eclipse-temurin:21-jre
WORKDIR /app
ARG JAR=target/app.jar
COPY --from=build /app/dependencies/ ./
COPY --from=build /app/spring-boot-loader/ ./
COPY --from=build /app/snapshot-dependencies/ ./
COPY --from=build /app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]Let the JVM see the container
Modern JVMs are container-aware, but you can tune how much of the container's memory the heap is allowed to use.
ENV JAVA_OPTS="-XX:MaxRAMPercentage=75.0"
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]Checklist for production images
- Use a JRE base image, not a full JDK, at runtime.
- Enable layered JARs so rebuilds only ship changed layers.
- Set
MaxRAMPercentageinstead of a fixed-Xmx. - Add a health check hitting
/actuator/health.
These steps typically cut image size and cold-start time noticeably while keeping the Dockerfile easy to read.