Home/Learn/Spring Boot/Docker & Deployment

Docker & Deployment

Intermediate
Production & Advanced

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

Dockerfile — multi-stage layered build for Spring Boot
# 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 — Spring Boot + PostgreSQL + Redis
# 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 volumes

Key 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 Aria
1

What is a multi-stage Docker build and why is it useful for Spring Boot?

EasyThoughtWorks
2

Why should the JVM process not run as root inside a container?

EasyAtlassian
3

How does Spring Boot's layered JAR improve Docker build caching?

MediumAmazon
4

Why use -XX:MaxRAMPercentage instead of -Xmx in a container?

MediumGoldman Sachs
5

How do you handle secret injection for a Spring Boot container in Kubernetes?

HardNetflix

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.

Loading discussion…