100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Java

Spring Boot with Docker

How to containerize Spring Boot applications using Dockerfiles, layered JARs, Cloud Native Buildpacks, and Docker Compose for local development.

Testing & PracticeIntermediate9 min readJul 10, 2026
Analogies

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.

dockerfile
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.

dockerfile
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-image uses Cloud Native Buildpacks to produce optimized OCI images without writing a Dockerfile.
  • spring-boot-docker-compose auto-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

Was this page helpful?

Topics covered

#Java#SpringBootStudyNotes#WebDevelopment#SpringBootWithDocker#Spring#Boot#Docker#Containerizing#StudyNotes#SkillVeris