Docker & Deployment
IntermediateA multi-stage Dockerfile builds a lean production image. Docker Compose orchestrates your Spring Boot app with its dependencies (PostgreSQL, Redis) for local development and testing.
Overview
A production Docker image for a Spring Boot app should be small (use JRE, not JDK), layer-optimized (dependencies layer rarely changes), and run as a non-root user. Multi-stage builds separate the build environment (JDK + Maven) from the runtime image (JRE only). Spring Boot's Buildpacks (./mvnw spring-boot:build-image) are an alternative that produces an OCI image without writing a Dockerfile.
Multi-Stage Dockerfile
Stage 1 compiles and packages the app. Stage 2 copies only the extracted layers into a minimal JRE image. Spring Boot's layered JARs separate dependencies, spring-boot-loader, snapshot dependencies, and application code — only the last layer changes on most rebuilds.
# Stage 1: Build with Maven + JDK
FROM eclipse-temurin:21-jdk-alpine AS builder
WORKDIR /app
# Download dependencies separately (cacheable layer)
COPY pom.xml .
COPY .mvn/ .mvn
COPY mvnw .
RUN ./mvnw dependency:go-offline -q
# Build
COPY src/ src/
RUN ./mvnw package -DskipTests -q
# Extract layered JAR (Spring Boot 2.3+)
RUN java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted
# Stage 2: Runtime — JRE only, no JDK or Maven
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# Non-root user for security
RUN addgroup -S spring && adduser -S spring -G spring
USER spring
# Copy layers from smallest-change (deps) to largest-change (app)
COPY --from=builder /app/target/extracted/dependencies/ ./
COPY --from=builder /app/target/extracted/spring-boot-loader/ ./
COPY --from=builder /app/target/extracted/snapshot-dependencies/ ./
COPY --from=builder /app/target/extracted/application/ ./
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s CMD wget -q http://localhost:8080/actuator/health -O- | grep -q UP
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "org.springframework.boot.loader.launch.JarLauncher"]Docker Compose for Local Development
Compose orchestrates your app alongside PostgreSQL and Redis. Use depends_on with healthcheck conditions to ensure the database is ready before Spring Boot starts.
# docker-compose.yml
version: '3.9'
services:
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: docker
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/toolhub
SPRING_DATASOURCE_USERNAME: toolhub
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD:-secret}
REDIS_HOST: redis
JWT_SECRET: ${JWT_SECRET:-dev-secret-change-in-prod}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: toolhub
POSTGRES_USER: toolhub
POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U toolhub -d toolhub"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD:-}
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
retries: 3
volumes:
postgres_data:
# Commands:
# docker compose up -d → start everything in background
# docker compose logs -f app → tail app logs
# docker compose down -v → stop and remove volumesKey Points to Remember
- 1Multi-stage builds keep production images small — only the JRE and app code, no JDK or Maven.
- 2Spring Boot's layered JARs optimize rebuild time — dependency layers rarely change between deploys.
- 3Always run the JVM process as a non-root user inside the container.
- 4Use -XX:MaxRAMPercentage=75.0 instead of -Xmx in containers — JVM reads the cgroup memory limit.
- 5depends_on with service_healthy waits for the database health check before starting the app.
- 6Buildpacks (spring-boot:build-image) generate a production image automatically without a Dockerfile.
Interview Questions
Sign in to ask AriaWhat is a multi-stage Docker build and why is it useful for Spring Boot?
Why should the JVM process not run as root inside a container?
How does Spring Boot's layered JAR improve Docker build caching?
Why use -XX:MaxRAMPercentage instead of -Xmx in a container?
How do you handle secret injection for a Spring Boot container in Kubernetes?
Ask Aria about 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.