Home/Learn/Spring Boot/Spring Boot with Docker & Deployment

Spring Boot with Docker & Deployment

Intermediate
Advanced

Spring Boot Maven/Gradle plugins build OCI-compliant images via Buildpacks or a Dockerfile; layered JARs reduce rebuild time by keeping dependencies in separate layers.

Overview

Containerising a Spring Boot application can be done three ways: a traditional Dockerfile, the Spring Boot Maven/Gradle plugin with Buildpacks (no Dockerfile needed), or layered JARs with a hand-crafted Dockerfile. Buildpacks (./mvnw spring-boot:build-image) produce optimised, security-hardened images with automatic layer caching and are the recommended approach for teams that want opinionated defaults. Layered JARs split the application archive into dependency, snapshot-dependency, resources, and application layers, so Docker only rebuilds the application layer on code changes — drastically speeding up incremental builds. Understanding the correct JVM flags for containers (MaxRAMPercentage, heap sizing) is essential to avoid OOMKills in Kubernetes.

Building images with Spring Boot Buildpacks

The Spring Boot plugin integrates with Paketo Buildpacks to create an OCI image without a Dockerfile. The plugin automatically selects the JDK, configures memory, adds security hardening, and creates efficient layers. Images are built with ./mvnw spring-boot:build-image and pushed to a registry. The image name can be customised in pom.xml.

XML + Shell — Buildpack image with registry publish
<!-- pom.xml — configure Buildpack image name and publish -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <name>registry.example.com/myapp:${project.version}</name>
            <publish>true</publish>
            <env>
                <!-- JVM memory tuning in container -->
                <BPL_JVM_THREAD_COUNT>50</BPL_JVM_THREAD_COUNT>
                <BPL_JVM_HEAP_PERCENT>75</BPL_JVM_HEAP_PERCENT>
            </env>
        </image>
        <docker>
            <publishRegistry>
                <url>registry.example.com</url>
                <username>${REGISTRY_USER}</username>
                <password>${REGISTRY_PASSWORD}</password>
            </publishRegistry>
        </docker>
    </configuration>
</plugin>

# Build and push
./mvnw spring-boot:build-image -DskipTests

# Run locally
docker run -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=dev \
  registry.example.com/myapp:1.0.0

Layered JARs with multi-stage Dockerfile

Spring Boot 2.3+ supports layered JARs: the archive is split into dependencies (rarely changed), snapshot-dependencies, spring-boot-loader, and application layers. A multi-stage Dockerfile extracts these layers, allowing Docker to cache the dependency layer across builds — only the application layer changes per commit.

Dockerfile — multi-stage layered JAR build for minimal rebuild time
# Step 1: enable layered JAR in pom.xml (default in Boot 2.3+)
# <layers><enabled>true</enabled></layers> in spring-boot-maven-plugin

# Inspect layers
java -Djarmode=layertools -jar target/myapp.jar list
# dependencies
# spring-boot-loader
# snapshot-dependencies
# application

# Multi-stage Dockerfile
FROM eclipse-temurin:21-jre AS builder
WORKDIR /app
COPY target/myapp.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:21-jre
WORKDIR /app

# Copy layers in order from least- to most-changed
# Docker caches unchanged layers between builds
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./

# Non-root user for security
RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser
USER appuser

EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

JVM container tuning and Kubernetes deployment

Without container-aware JVM flags, the JVM sees the host's total RAM and may size the heap too large, causing OOMKills. Use -XX:MaxRAMPercentage to set heap as a fraction of container memory. Combine with CPU flag -XX:ActiveProcessorCount to prevent thread-count over-provisioning. In Kubernetes, always set both requests and limits for predictable scheduling.

YAML — Kubernetes Deployment with JVM container flags and health probes
# Kubernetes Deployment with proper resource limits and JVM tuning
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: order-service
          image: registry.example.com/order-service:1.0.0
          ports:
            - containerPort: 8080
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: prod
            - name: JAVA_OPTS
              value: >-
                -XX:MaxRAMPercentage=75.0
                -XX:InitialRAMPercentage=50.0
                -XX:+UseG1GC
                -XX:+UseContainerSupport
                -Djava.security.egd=file:/dev/./urandom
          resources:
            requests:
              memory: "512Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"   # same as request to prevent burstable class
              cpu: "1000m"
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            initialDelaySeconds: 20
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 15

Key Points to Remember

  • 1Buildpacks (spring-boot:build-image) create optimised OCI images without a Dockerfile — recommended for standard deployments
  • 2Layered JARs (default in Boot 2.3+) split the archive into dependency/application layers, enabling Docker layer caching
  • 3Multi-stage Dockerfiles extract layers in order from least-changed to most-changed to maximise build cache reuse
  • 4-XX:MaxRAMPercentage and -XX:+UseContainerSupport ensure the JVM uses container memory limits, not host RAM
  • 5Non-root user in Dockerfile reduces attack surface — never run JVM containers as root
  • 6Set Kubernetes memory requests == limits to prevent the pod being placed in the Burstable QoS class and risk OOMKill

Interview Questions

Sign in to ask Aria
1

What are the three approaches to containerising a Spring Boot application and what are the trade-offs?

MediumThoughtworks
2

What is a layered JAR and how does it reduce Docker build time in CI/CD pipelines?

MediumAmazon
3

Without -XX:MaxRAMPercentage, what happens when a Spring Boot container runs in a memory-limited pod?

HardNetflix
4

Why should you set Kubernetes memory requests equal to limits for a Java service?

MediumGoogle
5

How would you configure liveness and readiness probes for a Spring Boot application in Kubernetes?

MediumUber

Ask Aria about Spring Boot with Docker & Deployment

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…