Containerizing a Spring Boot Application
Docker packages a Spring Boot application together with its JVM, dependencies, and OS-level libraries into a single portable image, eliminating the 'it works on my machine' problem when moving from a developer laptop to a CI runner or production cluster. A minimal Dockerfile starts from an OpenJDK or Eclipse Temurin base image, copies the built fat JAR produced by spring-boot-maven-plugin or the Gradle bootJar task, and defines an ENTRYPOINT that runs java -jar app.jar.
Cricket analogy: Shipping a Spring Boot image is like a franchise sending its entire playing eleven, coach, and kit bag together to an away IPL fixture rather than hoping the host venue has the right equipment.
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
COPY target/order-service-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]Multi-Stage Builds and Layered JARs
A naive Dockerfile that copies a single fat JAR rebuilds the entire layer on every code change, even if only one class changed, because Docker caches image layers and a modified JAR invalidates the whole layer. Spring Boot's layered JAR feature (enabled via spring-boot-maven-plugin's layers goal) splits the JAR into dependencies, spring-boot-loader, snapshot-dependencies, and application layers, and a multi-stage Dockerfile uses jar -xf or java -Djarmode=layertools -jar app.jar extract in a builder stage so that only the thin application layer changes between builds, keeping dependency layers cached.
Cricket analogy: Re-laminating an entire cricket bat every time you replace the grip is wasteful — layered JARs are like only re-doing the grip layer while keeping the willow and toe guard untouched between builds.
FROM eclipse-temurin:21-jdk-jammy AS builder
WORKDIR /app
COPY target/order-service-0.0.1-SNAPSHOT.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:21-jre-jammy
WORKDIR /app
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]Cloud Native Buildpacks and Local Development with Docker Compose
Instead of hand-writing a Dockerfile, Spring Boot's Maven and Gradle plugins support spring-boot:build-image, which uses Cloud Native Buildpacks (via Paketo) to automatically produce an optimized, layered OCI image without any Dockerfile at all — the buildpack detects the JAR, picks an appropriate JRE, and applies security patches on rebuild. For local development, Spring Boot 3.1+ ships spring-boot-docker-compose, which auto-detects a compose.yaml in the project root and starts/stops services like PostgreSQL or Redis automatically when you run the app from your IDE, wiring connection properties without manual configuration.
Cricket analogy: Using buildpacks instead of a hand-written Dockerfile is like trusting a professional curator to prepare a pitch to exact specifications rather than the groundstaff guessing dimensions themselves.
spring-boot-docker-compose reads service names and images in compose.yaml, matches them against known service connection detectors (Postgres, MySQL, Redis, Kafka, MongoDB, RabbitMQ), and automatically configures the corresponding DataSource or client beans — no manual application.yml datasource URL needed for local development.
Running the JVM inside a container without container-awareness flags can cause it to miscalculate available memory from the host rather than the container's cgroup limits, leading to OOMKilled pods; modern JDKs (10+) respect cgroup limits by default, but always still set explicit -XX:MaxRAMPercentage and container memory limits rather than assuming defaults are safe.
- A Dockerfile for Spring Boot typically copies the built fat JAR onto a slim JRE base image like eclipse-temurin.
- Layered JARs (spring-boot-maven-plugin layers goal) split the JAR so multi-stage builds only rebuild the volatile application layer.
- Multi-stage builds keep the final runtime image small by discarding the JDK/build tooling used only in the builder stage.
spring-boot:build-imageuses Cloud Native Buildpacks to produce optimized OCI images without writing a Dockerfile.spring-boot-docker-composeauto-starts and auto-configures services declared in compose.yaml for local development.- Always set container-aware JVM memory flags (e.g. -XX:MaxRAMPercentage) alongside explicit container memory limits.
- Exposing the correct port with EXPOSE and matching it to server.port keeps container networking predictable.
Practice what you learned
1. What is the main benefit of Spring Boot's layered JAR feature inside a multi-stage Dockerfile?
2. What does `spring-boot:build-image` use to build a container image without a Dockerfile?
3. What does spring-boot-docker-compose do when the app starts from an IDE?
4. Why is it risky to skip container-aware JVM memory settings?
Was this page helpful?
You May Also Like
Building Microservices with Spring Boot
How to design, discover, secure, and make resilient a set of independently deployable Spring Boot services using Spring Cloud.
Testing Spring Boot Applications
A practical guide to layering unit, slice, and integration tests in Spring Boot using JUnit 5, Mockito, MockMvc, and Testcontainers.
Spring Boot Quick Reference
A condensed cheat sheet of the most-used Spring Boot annotations, configuration patterns, Actuator endpoints, and CLI commands.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics
Web DevelopmentNext.js Study Notes
JavaScript · 30 topics