google-site-verification: google61fe8ba583a51912.html
Multi-Stage Docker Builds for Java Applications

Multi-stage Docker builds are a powerful feature that allows you to optimize your Java application Docker images by separating build dependencies from runtime dependencies. This article explores how to create efficient, secure, and production-ready Docker images for Java applications.

Basic Multi-Stage Docker Build

1. Simple Multi-Stage Maven Build

# Build stage FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Runtime stage FROM openjdk:17-jre-slim WORKDIR /app # Copy the built JAR from builder stage COPY --from=builder /app/target/*.jar app.jar # Security: Run as non-root user RUN groupadd -r spring && useradd -r -g spring spring USER spring EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

2. Multi-Stage with Gradle

# Build stage with Gradle FROM gradle:7.4.2-jdk17 AS builder WORKDIR /app COPY build.gradle . COPY settings.gradle . COPY src ./src RUN gradle clean build -x test # Runtime stage FROM openjdk:17-jre-slim WORKDIR /app # Copy the built JAR COPY --from=builder /app/build/libs/*.jar app.jar # Create non-root user RUN addgroup --system --gid 1001 appgroup && \ adduser --system --uid 1001 --gid 1001 appuser USER appuser EXPOSE 8080 ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Advanced Multi-Stage Patterns

3. Multi-Stage with Dependency Caching

# Stage 1: Dependency resolution FROM maven:3.8.5-openjdk-17 AS deps WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline # Stage 2: Build application FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY --from=deps /root/.m2 /root/.m2 COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Stage 3: Runtime environment FROM openjdk:17-jre-slim AS runtime WORKDIR /app # Install security updates RUN apt-get update && \ apt-get upgrade -y && \ rm -rf /var/lib/apt/lists/* # Copy application COPY --from=builder /app/target/*.jar app.jar # Security hardening RUN groupadd -r spring && useradd -r -g spring spring && \ chown -R spring:spring /app USER spring # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8080/actuator/health || exit 1 EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

4. Multi-Architecture Build

# Build stage for multiple architectures FROM --platform=$BUILDPLATFORM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Runtime stage FROM openjdk:17-jre-slim WORKDIR /app # Install necessary packages for your application RUN apt-get update && \ apt-get install -y --no-install-recommends \ curl \ && rm -rf /var/lib/apt/lists/* COPY --from=builder /app/target/*.jar app.jar # Create non-root user RUN addgroup --system --gid 1001 appgroup && \ adduser --system --uid 1001 --gid 1001 appuser USER appuser EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

Spring Boot Specific Optimizations

5. Spring Boot with Layered Jars

# Build stage FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Extract layers FROM builder AS extractor WORKDIR /app RUN java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted # Runtime stage FROM openjdk:17-jre-slim WORKDIR /app # Copy layers from extractor COPY --from=extractor /app/target/extracted/dependencies/ ./ COPY --from=extractor /app/target/extracted/spring-boot-loader/ ./ COPY --from=extractor /app/target/extracted/snapshot-dependencies/ ./ COPY --from=extractor /app/target/extracted/application/ ./ # Create non-root user RUN groupadd -r spring && useradd -r -g spring spring USER spring EXPOSE 8080 ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

6. Spring Boot with JVM Optimization

# Build stage FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Runtime stage with JVM optimizations FROM openjdk:17-jre-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar # JVM optimization flags ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions" # Create non-root user RUN groupadd -r spring && useradd -r -g spring spring USER spring EXPOSE 8080 ENTRYPOINT exec java $JAVA_OPTS -jar app.jar

Security Hardened Builds

7. Security-Focused Multi-Stage Build

# Build stage FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # Security scan stage (optional) FROM aquasec/trivy:latest AS security-scan WORKDIR /app COPY --from=builder /app/target/*.jar . RUN trivy filesystem --exit-code 1 --no-progress / # Runtime stage with security hardening FROM openjdk:17-jre-slim WORKDIR /app # Security updates and cleanup RUN apt-get update && \ apt-get upgrade -y && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* # Copy application COPY --from=builder /app/target/*.jar app.jar # Security: Non-root user with specific UID/GID RUN groupadd -g 1000 appuser && \ useradd -r -u 1000 -g appuser appuser && \ chown -R appuser:appuser /app USER 1000 # Security: Read-only filesystem RUN chmod -R go-w /app EXPOSE 8080 # Use exec form for better signal handling ENTRYPOINT ["java", "-jar", "app.jar"]

8. Distroless Java Image

# Build stage FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Runtime stage with Google Distroless FROM gcr.io/distroless/java17:nonroot WORKDIR /app COPY --from=builder /app/target/*.jar app.jar # Distroless images run as non-root by default EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

Advanced Build Scenarios

9. Multi-Module Maven Project

# Stage 1: Build all modules FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app # Copy parent POM and all module POMs COPY pom.xml . COPY api/pom.xml api/pom.xml COPY service/pom.xml service/pom.xml COPY web/pom.xml web/pom.xml # Download dependencies RUN mvn dependency:go-offline # Copy source code COPY api/src api/src COPY service/src service/src COPY web/src web/src # Build all modules RUN mvn clean package -DskipTests # Stage 2: Runtime for web module FROM openjdk:17-jre-slim WORKDIR /app COPY --from=builder /app/web/target/*.jar app.jar RUN groupadd -r app && useradd -r -g app app USER app EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

10. Custom JRE with jlink

# Build stage with JDK FROM openjdk:17-jdk-slim AS builder WORKDIR /app COPY . . RUN ./mvnw clean package -DskipTests # Create custom JRE stage FROM openjdk:17-jdk-slim AS jre-builder RUN jlink \ --add-modules java.base,java.logging,java.management,java.naming,java.sql,java.desktop,java.security.jgss \ --strip-debug \ --no-man-pages \ --no-header-files \ --compress=2 \ --output /custom-jre # Final stage with custom JRE FROM debian:bullseye-slim WORKDIR /app # Copy custom JRE COPY --from=jre-builder /custom-jre /usr/local/java # Set Java environment ENV JAVA_HOME=/usr/local/java ENV PATH="${JAVA_HOME}/bin:${PATH}" # Copy application COPY --from=builder /app/target/*.jar app.jar # Create non-root user RUN groupadd -r app && useradd -r -g app app USER app EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

Environment-Specific Builds

11. Profile-Based Build Configuration

# Build stage with profile support FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY pom.xml . COPY src ./src ARG BUILD_PROFILE=prod RUN mvn clean package -DskipTests -P${BUILD_PROFILE} # Runtime stage FROM openjdk:17-jre-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar # Set environment based on build profile ARG BUILD_PROFILE=prod ENV SPRING_PROFILES_ACTIVE=${BUILD_PROFILE} RUN groupadd -r app && useradd -r -g app app USER app EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

12. Development vs Production Builds

Dockerfile.dev:

# Development build with hot reload FROM maven:3.8.5-openjdk-17 WORKDIR /app COPY . . # Install DevTools for hot reload RUN mvn clean compile EXPOSE 8080 CMD ["mvn", "spring-boot:run"]

Dockerfile.prod:

# Production multi-stage build FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM openjdk:17-jre-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar RUN groupadd -r app && useradd -r -g app app USER app EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

Build Optimization Techniques

13. Docker BuildKit Optimization

# syntax=docker/dockerfile:1.4 # Build stage with cache mounts FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app # Cache Maven dependencies COPY pom.xml . RUN --mount=type=cache,target=/root/.m2 mvn dependency:go-offline # Build application COPY src ./src RUN --mount=type=cache,target=/root/.m2 mvn clean package -DskipTests # Runtime stage FROM openjdk:17-jre-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar RUN groupadd -r app && useradd -r -g app app USER app EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

14. Multi-Stage with Testing

# Test stage FROM maven:3.8.5-openjdk-17 AS tester WORKDIR /app COPY pom.xml . COPY src ./src RUN mvn test # Build stage FROM maven:3.8.5-openjdk-17 AS builder WORKDIR /app COPY --from=tester /app/target/surefire-reports /app/target/surefire-reports COPY pom.xml . COPY src ./src RUN mvn clean package -DskipTests # Runtime stage FROM openjdk:17-jre-slim WORKDIR /app COPY --from=builder /app/target/*.jar app.jar RUN groupadd -r app && useradd -r -g app app USER app EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"]

Docker Compose Integration

15. docker-compose.yml for Multi-Stage Builds

version: '3.8' services: app: build: context: . dockerfile: Dockerfile target: builder # You can specify which stage to build ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=docker depends_on: - postgres networks: - app-network postgres: image: postgres:13 environment: POSTGRES_DB: myapp POSTGRES_USER: appuser POSTGRES_PASSWORD: apppass volumes: - postgres_data:/var/lib/postgresql/data networks: - app-network volumes: postgres_data: networks: app-network: driver: bridge

Build Scripts and CI/CD Integration

16. Build Script Examples

build.sh:

#!/bin/bash # Build with different profiles build_image() { local profile=$1 local tag=$2 docker build \ --build-arg BUILD_PROFILE=$profile \ -t myapp:$tag \ . } # Build development image build_image "dev" "latest-dev" # Build production image build_image "prod" "latest" # Security scan docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ aquasec/trivy:latest \ image myapp:latest

Jenkinsfile:

pipeline { agent any stages { stage('Build') { steps { script { docker.build("myapp:${env.BUILD_ID}", "--target builder .") } } } stage('Test') { steps { sh 'docker run myapp:${env.BUILD_ID} mvn test' } } stage('Security Scan') { steps { sh 'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image myapp:${env.BUILD_ID}' } } stage('Production Build') { steps { script { docker.build("myapp:prod-${env.BUILD_ID}", ".") } } } } }

Best Practices Summary

Key Benefits of Multi-Stage Builds:

  1. Smaller Image Sizes: Eliminate build dependencies from final image
  2. Improved Security: Fewer packages and non-root users
  3. Better Caching: Separate dependency and source code layers
  4. Reproducible Builds: Consistent build environment
  5. CI/CD Optimization: Faster builds with layer caching

Essential Security Practices:

  • Use minimal base images (slim, alpine, distroless)
  • Run as non-root user
  • Regularly update base images
  • Scan for vulnerabilities
  • Use trusted base images

Performance Optimizations:

  • Leverage Docker BuildKit
  • Use cache mounts for package managers
  • Separate dependency resolution from build
  • Use .dockerignore to exclude unnecessary files

By implementing these multi-stage Docker build patterns, you can create optimized, secure, and efficient Java application containers suitable for production deployment.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper