Multi-Stage Builds Optimization in Java: Maximizing Docker Efficiency

Multi-stage builds optimize Docker images by separating build-time dependencies from runtime dependencies, resulting in smaller, more secure, and faster images.


Core Concepts

What are Multi-Stage Builds?

  • Multiple FROM statements in a single Dockerfile
  • Each stage can have different base images and dependencies
  • Copy artifacts from one stage to another
  • Final image contains only runtime dependencies

Benefits:

  • Smaller Images: Exclude build tools and dependencies
  • Improved Security: Fewer attack surfaces
  • Better Caching: Independent layer caching per stage
  • Simplified Build Process: Single Dockerfile for build and runtime

Dependencies and Setup

Project Structure
project/
├── src/
│   └── main/
│       └── java/
│           └── com/
│               └── example/
│                   └── Application.java
├── Dockerfile
├── Dockerfile.multistage
├── docker-compose.yml
├── pom.xml
└── README.md
Maven Dependencies
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-surefire-plugin.version>3.0.0</maven-surefire-plugin.version>
<spring-boot.version>3.1.0</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.example.Application</mainClass>
<finalName>${project.artifactId}</finalName>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
</plugin>
</plugins>
</build>

Dockerfile Implementations

1. Basic Multi-Stage Build
# Dockerfile.multistage
# Stage 1: Build stage
FROM maven:3.9.4-eclipse-temurin-17 AS builder
# Set working directory
WORKDIR /app
# Copy pom.xml and download dependencies
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Copy source code
COPY src ./src
# Build the application
RUN mvn clean package -DskipTests
# Stage 2: Runtime stage
FROM eclipse-temurin:17-jre-jammy
# Install security updates
RUN apt-get update && \
apt-get upgrade -y && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r spring && useradd -r -g spring spring
USER spring
# Set working directory
WORKDIR /app
# Copy JAR from build stage
COPY --from=builder --chown=spring:spring /app/target/*.jar app.jar
# Expose port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# Run the application
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
2. Advanced Multi-Stage with Optimization
# Dockerfile.optimized
# Stage 1: Dependency resolution
FROM maven:3.9.4-eclipse-temurin-17 AS deps
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Stage 2: Test stage (optional)
FROM deps AS tester
COPY src ./src
RUN mvn test -B
# Stage 3: Builder stage
FROM deps AS builder
COPY src ./src
RUN mvn clean package -DskipTests
# Extract layers for better caching
RUN java -Djarmode=layertools -jar target/*.jar extract --destination target/extracted
# Stage 4: Runtime stage
FROM eclipse-temurin:17-jre-jammy AS runtime
# Security hardening
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends curl && \
rm -rf /var/lib/apt/lists/* && \
apt-get clean
# Create application user
RUN groupadd -r spring && useradd --no-log-init -r -g spring spring
# Create app directory
RUN mkdir -p /app && chown spring:spring /app
WORKDIR /app
USER spring
# Copy layers from builder stage
COPY --from=builder --chown=spring:spring /app/target/extracted/dependencies/ ./
COPY --from=builder --chown=spring:spring /app/target/extracted/spring-boot-loader/ ./
COPY --from=builder --chown=spring:spring /app/target/extracted/snapshot-dependencies/ ./
COPY --from=builder --chown=spring:spring /app/target/extracted/application/ ./
# JVM optimization
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
EXPOSE 8080
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
3. Distroless-Based Multi-Stage
# Dockerfile.distroless
# Stage 1: Build stage
FROM maven:3.9.4-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
# Stage 2: Runtime with Distroless
FROM gcr.io/distroless/java17-debian11:nonroot
# Copy application
COPY --from=builder /app/target/*.jar /app/app.jar
# Use non-root user (provided by distroless)
USER nonroot
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
4. Alpine Linux Multi-Stage
# Dockerfile.alpine
# Stage 1: Build stage
FROM maven:3.9.4-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
# Stage 2: Alpine runtime
FROM eclipse-temurin:17-jre-alpine
# Install minimal packages
RUN apk update && \
apk upgrade && \
apk add --no-cache curl && \
rm -rf /var/cache/apk/*
# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring
USER spring
WORKDIR /app
COPY --from=builder --chown=spring:spring /app/target/*.jar app.jar
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
5. GraalVM Native Image Multi-Stage
# Dockerfile.native
# Stage 1: Build stage
FROM maven:3.9.4-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
# Stage 2: GraalVM native image build
FROM ghcr.io/graalvm/native-image:ol8-java17-22 AS native-builder
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
# Build native image
RUN native-image -jar app.jar --no-fallback -H:Name=application
# Stage 3: Minimal runtime
FROM oraclelinux:8-slim
# Install security updates
RUN microdnf update -y && \
microdnf install -y gcompat && \
microdnf clean all
# Create non-root user
RUN groupadd -r spring && useradd -r -g spring spring
USER spring
WORKDIR /app
COPY --from=native-builder --chown=spring:spring /app/application .
EXPOSE 8080
ENTRYPOINT ["./application"]

Java Build Optimization Classes

1. Docker Build Service
@Service
@Slf4j
public class DockerBuildService {
private final DockerClient dockerClient;
private final BuildOptimizationService optimizationService;
public DockerBuildService(DockerClient dockerClient, 
BuildOptimizationService optimizationService) {
this.dockerClient = dockerClient;
this.optimizationService = optimizationService;
}
public BuildResult buildMultiStageImage(BuildRequest request) {
log.info("Building multi-stage Docker image: {}", request.getImageName());
try {
// Analyze project for optimization opportunities
ProjectAnalysis analysis = optimizationService.analyzeProject(request.getProjectPath());
// Select appropriate Dockerfile template
String dockerfileContent = selectDockerfileTemplate(request, analysis);
// Build image
String imageId = buildImage(request, dockerfileContent);
// Analyze built image
ImageAnalysis imageAnalysis = analyzeBuiltImage(imageId);
return BuildResult.success(imageId, imageAnalysis);
} catch (Exception e) {
log.error("Docker build failed", e);
return BuildResult.failure("Build failed: " + e.getMessage());
}
}
public BuildResult buildOptimizedImage(OptimizedBuildRequest request) {
log.info("Building optimized image with strategy: {}", request.getOptimizationStrategy());
try {
// Generate optimized Dockerfile
String dockerfileContent = optimizationService.generateOptimizedDockerfile(request);
// Build with cache optimization
BuildCommand buildCommand = BuildCommand.builder()
.dockerfileContent(dockerfileContent)
.contextPath(request.getProjectPath())
.imageName(request.getImageName())
.cacheFrom(request.getCacheFrom())
.buildArgs(request.getBuildArgs())
.build();
String imageId = executeBuildWithCache(buildCommand);
// Perform post-build optimization
optimizationService.optimizeImageLayers(imageId);
return BuildResult.success(imageId, analyzeBuiltImage(imageId));
} catch (Exception e) {
log.error("Optimized build failed", e);
return BuildResult.failure("Optimized build failed: " + e.getMessage());
}
}
public ImageComparison compareBuildStrategies(String projectPath) {
List<BuildStrategyResult> results = new ArrayList<>();
// Test different build strategies
BuildStrategy[] strategies = BuildStrategy.values();
for (BuildStrategy strategy : strategies) {
BuildResult result = testBuildStrategy(projectPath, strategy);
results.add(new BuildStrategyResult(strategy, result));
}
return new ImageComparison(results);
}
private String selectDockerfileTemplate(BuildRequest request, ProjectAnalysis analysis) {
if (analysis.isSpringBoot()) {
return generateSpringBootDockerfile(request, analysis);
} else if (analysis.isNativeCompatible()) {
return generateNativeImageDockerfile(request, analysis);
} else {
return generateStandardJavaDockerfile(request, analysis);
}
}
private String generateSpringBootDockerfile(BuildRequest request, ProjectAnalysis analysis) {
return """
# Multi-stage build for Spring Boot application
FROM maven:3.9.4-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre-jammy
RUN groupadd -r spring && useradd -r -g spring spring
USER spring
WORKDIR /app
COPY --from=builder --chown=spring:spring /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
""";
}
// Additional implementation details would go here...
}
2. Build Optimization Service
@Service
@Slf4j
public class BuildOptimizationService {
private final ProjectAnalyzer projectAnalyzer;
private final DockerfileGenerator dockerfileGenerator;
public ProjectAnalysis analyzeProject(String projectPath) {
log.info("Analyzing project at: {}", projectPath);
ProjectAnalysis analysis = new ProjectAnalysis();
try {
// Analyze project type
analysis.setProjectType(analyzeProjectType(projectPath));
// Check dependencies
analysis.setDependencies(analyzeDependencies(projectPath));
// Calculate size metrics
analysis.setSizeMetrics(calculateSizeMetrics(projectPath));
// Check for optimization opportunities
analysis.setOptimizations(findOptimizations(analysis));
return analysis;
} catch (Exception e) {
log.error("Project analysis failed", e);
throw new BuildOptimizationException("Project analysis failed", e);
}
}
public String generateOptimizedDockerfile(OptimizedBuildRequest request) {
ProjectAnalysis analysis = analyzeProject(request.getProjectPath());
DockerfileTemplate template = DockerfileTemplate.builder()
.baseImage(selectBaseImage(analysis, request.getOptimizationStrategy()))
.buildStages(generateBuildStages(analysis))
.runtimeConfig(generateRuntimeConfig(analysis))
.optimizations(generateOptimizations(analysis))
.build();
return dockerfileGenerator.generate(template);
}
public void optimizeImageLayers(String imageId) {
try {
// Remove unnecessary files
removeBuildArtifacts(imageId);
// Clean package caches
cleanPackageCaches(imageId);
// Optimize file permissions
optimizePermissions(imageId);
} catch (Exception e) {
log.warn("Image optimization failed: {}", e.getMessage());
}
}
public BuildReport generateBuildReport(BuildResult result) {
return BuildReport.builder()
.imageId(result.getImageId())
.sizeAnalysis(analyzeImageSize(result.getImageId()))
.layerAnalysis(analyzeImageLayers(result.getImageId()))
.securityAnalysis(analyzeSecurity(result.getImageId()))
.optimizationSuggestions(generateSuggestions(result))
.build();
}
private ProjectType analyzeProjectType(String projectPath) {
File pomFile = new File(projectPath, "pom.xml");
File gradleFile = new File(projectPath, "build.gradle");
if (pomFile.exists()) {
return analyzeMavenProject(pomFile);
} else if (gradleFile.exists()) {
return ProjectType.GRADLE;
} else {
return ProjectType.UNKNOWN;
}
}
private ProjectType analyzeMavenProject(File pomFile) {
try {
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(new FileReader(pomFile));
// Check for Spring Boot
if (model.getParent() != null && 
model.getParent().getArtifactId().contains("spring-boot")) {
return ProjectType.SPRING_BOOT;
}
// Check for Quarkus
if (hasDependency(model, "io.quarkus")) {
return ProjectType.QUARKUS;
}
// Check for Micronaut
if (hasDependency(model, "io.micronaut")) {
return ProjectType.MICRONAUT;
}
return ProjectType.STANDARD_MAVEN;
} catch (Exception e) {
log.warn("Failed to analyze Maven project", e);
return ProjectType.STANDARD_MAVEN;
}
}
private boolean hasDependency(Model model, String groupId) {
return model.getDependencies().stream()
.anyMatch(dep -> dep.getGroupId().startsWith(groupId));
}
// Additional analysis methods...
}
3. Project Analyzer
@Component
@Slf4j
public class ProjectAnalyzer {
public DependencyAnalysis analyzeDependencies(String projectPath) {
DependencyAnalysis analysis = new DependencyAnalysis();
try {
File pomFile = new File(projectPath, "pom.xml");
if (pomFile.exists()) {
return analyzeMavenDependencies(pomFile);
}
// Add Gradle support here
} catch (Exception e) {
log.error("Dependency analysis failed", e);
}
return analysis;
}
public SizeMetrics calculateSizeMetrics(String projectPath) {
SizeMetrics metrics = new SizeMetrics();
try {
// Calculate source code size
long sourceSize = calculateDirectorySize(new File(projectPath, "src"));
metrics.setSourceSize(sourceSize);
// Calculate dependency size
long dependencySize = calculateDependencySize(projectPath);
metrics.setDependencySize(dependencySize);
// Calculate build output size
long buildOutputSize = calculateBuildOutputSize(projectPath);
metrics.setBuildOutputSize(buildOutputSize);
} catch (Exception e) {
log.warn("Size calculation failed", e);
}
return metrics;
}
public List<Optimization> findOptimizations(ProjectAnalysis analysis) {
List<Optimization> optimizations = new ArrayList<>();
// Check for large dependencies that could be optimized
if (analysis.getDependencySize() > 100 * 1024 * 1024) { // 100MB
optimizations.add(Optimization.DEPENDENCY_OPTIMIZATION);
}
// Check if application can use smaller base image
if (analysis.isCompatibleWithDistroless()) {
optimizations.add(Optimization.DISTROLESS_BASE);
}
// Check for native image compatibility
if (analysis.isNativeCompatible()) {
optimizations.add(Optimization.NATIVE_IMAGE);
}
// Check for layer optimization opportunities
if (analysis.hasLayerSplittingOpportunities()) {
optimizations.add(Optimization.LAYER_OPTIMIZATION);
}
return optimizations;
}
private DependencyAnalysis analyzeMavenDependencies(File pomFile) throws Exception {
DependencyAnalysis analysis = new DependencyAnalysis();
MavenXpp3Reader reader = new MavenXpp3Reader();
Model model = reader.read(new FileReader(pomFile));
// Analyze dependencies
List<DependencyInfo> dependencies = model.getDependencies().stream()
.map(this::mapDependency)
.collect(Collectors.toList());
analysis.setDependencies(dependencies);
analysis.setTotalDependencies(dependencies.size());
// Calculate approximate size (this would require more sophisticated analysis)
long estimatedSize = dependencies.stream()
.mapToLong(DependencyInfo::getEstimatedSize)
.sum();
analysis.setEstimatedSize(estimatedSize);
return analysis;
}
private DependencyInfo mapDependency(org.apache.maven.model.Dependency mavenDep) {
return DependencyInfo.builder()
.groupId(mavenDep.getGroupId())
.artifactId(mavenDep.getArtifactId())
.version(mavenDep.getVersion())
.scope(mavenDep.getScope() != null ? mavenDep.getScope() : "compile")
.build();
}
private long calculateDirectorySize(File directory) {
if (!directory.exists()) return 0;
return Arrays.stream(directory.listFiles())
.mapToLong(file -> {
if (file.isDirectory()) {
return calculateDirectorySize(file);
} else {
return file.length();
}
})
.sum();
}
}
4. Dockerfile Generator
@Component
public class DockerfileGenerator {
public String generate(DockerfileTemplate template) {
StringBuilder dockerfile = new StringBuilder();
// Generate build stages
for (BuildStage stage : template.getBuildStages()) {
dockerfile.append(generateStage(stage)).append("\n\n");
}
// Generate runtime stage
dockerfile.append(generateRuntimeStage(template.getRuntimeConfig()));
return dockerfile.toString();
}
public String generateForStrategy(ProjectAnalysis analysis, BuildStrategy strategy) {
switch (strategy) {
case STANDARD_MULTI_STAGE:
return generateStandardMultiStage(analysis);
case DISTROLESS:
return generateDistroless(analysis);
case ALPINE:
return generateAlpine(analysis);
case NATIVE:
return generateNative(analysis);
case LAYER_OPTIMIZED:
return generateLayerOptimized(analysis);
default:
return generateStandardMultiStage(analysis);
}
}
private String generateStandardMultiStage(ProjectAnalysis analysis) {
return """
# Standard multi-stage build
FROM maven:3.9.4-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM eclipse-temurin:17-jre-jammy
RUN groupadd -r spring && useradd -r -g spring spring
USER spring
WORKDIR /app
COPY --from=builder --chown=spring:spring /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
""";
}
private String generateDistroless(ProjectAnalysis analysis) {
return """
# Distroless multi-stage build
FROM maven:3.9.4-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn clean package -DskipTests
FROM gcr.io/distroless/java17-debian11:nonroot
COPY --from=builder /app/target/*.jar /app/app.jar
USER nonroot
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
""";
}
private String generateStage(BuildStage stage) {
return String.format("""
# %s
FROM %s AS %s
WORKDIR %s
%s
""", 
stage.getDescription(),
stage.getBaseImage(),
stage.getName(),
stage.getWorkDir(),
String.join("\n", stage.getCommands())
);
}
}

Models and Data Transfer Objects

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BuildRequest {
@NotBlank
private String projectPath;
@NotBlank
private String imageName;
private String dockerfilePath;
private Map<String, String> buildArgs;
private List<String> cacheFrom;
private boolean pushToRegistry;
private String registryUrl;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OptimizedBuildRequest {
private String projectPath;
private String imageName;
private BuildStrategy optimizationStrategy;
private List<String> cacheFrom;
private Map<String, String> buildArgs;
private boolean securityScan;
private boolean sizeOptimization;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BuildResult {
private boolean success;
private String imageId;
private String errorMessage;
private long buildTime;
private long imageSize;
private ImageAnalysis imageAnalysis;
public static BuildResult success(String imageId, ImageAnalysis analysis) {
return BuildResult.builder()
.success(true)
.imageId(imageId)
.imageAnalysis(analysis)
.buildTime(System.currentTimeMillis())
.build();
}
public static BuildResult failure(String errorMessage) {
return BuildResult.builder()
.success(false)
.errorMessage(errorMessage)
.buildTime(System.currentTimeMillis())
.build();
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProjectAnalysis {
private ProjectType projectType;
private DependencyAnalysis dependencies;
private SizeMetrics sizeMetrics;
private List<Optimization> optimizations;
private boolean springBoot;
private boolean nativeCompatible;
private boolean compatibleWithDistroless;
public long getDependencySize() {
return dependencies != null ? dependencies.getEstimatedSize() : 0;
}
public boolean hasLayerSplittingOpportunities() {
return dependencies != null && dependencies.getTotalDependencies() > 10;
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DependencyAnalysis {
private List<DependencyInfo> dependencies;
private int totalDependencies;
private long estimatedSize;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class DependencyInfo {
private String groupId;
private String artifactId;
private String version;
private String scope;
private long estimatedSize;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SizeMetrics {
private long sourceSize;
private long dependencySize;
private long buildOutputSize;
private long totalSize;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ImageAnalysis {
private long totalSize;
private int layerCount;
private List<LayerInfo> layers;
private String baseImage;
private boolean hasSecurityIssues;
private List<String> securityIssues;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LayerInfo {
private String id;
private long size;
private String command;
private boolean cacheable;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class BuildReport {
private String imageId;
private SizeAnalysis sizeAnalysis;
private LayerAnalysis layerAnalysis;
private SecurityAnalysis securityAnalysis;
private List<OptimizationSuggestion> optimizationSuggestions;
private BuildStrategy recommendedStrategy;
}
public enum ProjectType {
SPRING_BOOT, QUARKUS, MICRONAUT, STANDARD_MAVEN, GRADLE, UNKNOWN
}
public enum BuildStrategy {
STANDARD_MULTI_STAGE,
DISTROLESS,
ALPINE,
NATIVE,
LAYER_OPTIMIZED,
MINIMAL_JRE
}
public enum Optimization {
DEPENDENCY_OPTIMIZATION,
DISTROLESS_BASE,
NATIVE_IMAGE,
LAYER_OPTIMIZATION,
CACHE_OPTIMIZATION,
SECURITY_HARDENING
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class DockerfileTemplate {
private String baseImage;
private List<BuildStage> buildStages;
private RuntimeConfig runtimeConfig;
private List<Optimization> optimizations;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class BuildStage {
private String name;
private String baseImage;
private String description;
private String workDir;
private List<String> commands;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class RuntimeConfig {
private String baseImage;
private String user;
private String workDir;
private List<String> environmentVariables;
private List<String> exposedPorts;
private String entrypoint;
private String healthCheck;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class BuildCommand {
private String dockerfileContent;
private String contextPath;
private String imageName;
private List<String> cacheFrom;
private Map<String, String> buildArgs;
private boolean noCache;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class BuildStrategyResult {
private BuildStrategy strategy;
private BuildResult result;
private long buildTime;
private long imageSize;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class ImageComparison {
private List<BuildStrategyResult> strategyResults;
private BuildStrategyResult bestStrategy;
public ImageComparison(List<BuildStrategyResult> strategyResults) {
this.strategyResults = strategyResults;
this.bestStrategy = findBestStrategy(strategyResults);
}
private BuildStrategyResult findBestStrategy(List<BuildStrategyResult> results) {
return results.stream()
.filter(result -> result.getResult().isSuccess())
.min(Comparator.comparingLong(BuildStrategyResult::getImageSize))
.orElse(null);
}
}

Build Optimization Controller

@RestController
@RequestMapping("/api/docker")
@Slf4j
public class DockerBuildController {
private final DockerBuildService dockerBuildService;
private final BuildOptimizationService optimizationService;
public DockerBuildController(DockerBuildService dockerBuildService,
BuildOptimizationService optimizationService) {
this.dockerBuildService = dockerBuildService;
this.optimizationService = optimizationService;
}
@PostMapping("/build/multi-stage")
public ResponseEntity<BuildResult> buildMultiStage(@Valid @RequestBody BuildRequest request) {
log.info("Building multi-stage image for project: {}", request.getProjectPath());
BuildResult result = dockerBuildService.buildMultiStageImage(request);
if (result.isSuccess()) {
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
}
}
@PostMapping("/build/optimized")
public ResponseEntity<BuildResult> buildOptimized(@Valid @RequestBody OptimizedBuildRequest request) {
log.info("Building optimized image with strategy: {}", request.getOptimizationStrategy());
BuildResult result = dockerBuildService.buildOptimizedImage(request);
if (result.isSuccess()) {
return ResponseEntity.ok(result);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
}
}
@GetMapping("/analyze/{projectPath}")
public ResponseEntity<ProjectAnalysis> analyzeProject(@PathVariable String projectPath) {
ProjectAnalysis analysis = optimizationService.analyzeProject(projectPath);
return ResponseEntity.ok(analysis);
}
@GetMapping("/compare/{projectPath}")
public ResponseEntity<ImageComparison> compareBuildStrategies(@PathVariable String projectPath) {
ImageComparison comparison = dockerBuildService.compareBuildStrategies(projectPath);
return ResponseEntity.ok(comparison);
}
@GetMapping("/report/{imageId}")
public ResponseEntity<BuildReport> getBuildReport(@PathVariable String imageId) {
BuildResult result = BuildResult.builder().imageId(imageId).build();
BuildReport report = optimizationService.generateBuildReport(result);
return ResponseEntity.ok(report);
}
@PostMapping("/optimize/{imageId}")
public ResponseEntity<BuildResult> optimizeExistingImage(@PathVariable String imageId) {
// This would implement image optimization techniques
return ResponseEntity.ok(BuildResult.success(imageId, null));
}
}

Docker Compose for Multi-Stage Builds

# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.multistage
image: my-app:latest
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
app-optimized:
build:
context: .
dockerfile: Dockerfile.optimized
image: my-app:optimized
ports:
- "8081:8080"
app-distroless:
build:
context: .
dockerfile: Dockerfile.distroless
image: my-app:distroless
ports:
- "8082:8080"
build-analyzer:
build:
context: .
dockerfile: Dockerfile.analyzer
volumes:
- .:/project
command: analyze /project

Best Practices and Optimization Tips

1. Layer Caching Optimization
# Optimized for layer caching
FROM maven:3.9.4-eclipse-temurin-17 AS builder
# Copy POM first for better caching
WORKDIR /app
COPY pom.xml .
# Download dependencies (cached unless POM changes)
RUN mvn dependency:go-offline -B
# Copy source code (cached unless source changes)
COPY src ./src
# Build application
RUN mvn clean package -DskipTests
2. Security Hardening
# Security-hardened multi-stage build
FROM eclipse-temurin:17-jre-jammy
# Update system and install security patches
RUN apt-get update && \
apt-get upgrade -y && \
rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN groupadd -r spring && useradd --no-log-init -r -g spring spring
# Use non-privileged user
USER spring
# Copy application
COPY --from=builder --chown=spring:spring /app/target/*.jar app.jar
# Don't run as root
USER spring
3. JVM Optimization
# JVM-optimized runtime
FROM eclipse-temurin:17-jre-jammy
# JVM optimization for containers
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
# Run application with optimized JVM settings
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

Performance Comparison

StrategyImage SizeBuild TimeSecurityStartup Time
Standard Multi-Stage~200MBMediumGoodFast
Distroless~50MBMediumExcellentFast
Alpine~150MBFastGoodFast
Native Image~50MBSlowExcellentVery Fast
Layer Optimized~180MBMediumGoodFast

Conclusion

Multi-stage builds optimization in Java provides:

  • Significantly Smaller Images: From 600MB+ to 50-200MB
  • Improved Security: Minimal attack surface with distroless images
  • Better Build Performance: Layer caching and parallel builds
  • Production Readiness: Optimized for container orchestration
  • Cost Efficiency: Reduced storage and bandwidth costs

By implementing the strategies shown above, you can create highly optimized Docker images that are secure, fast, and efficient for production deployment. The combination of multi-stage builds, appropriate base images, and JVM optimization results in enterprise-grade containerized applications.

Pyroscope Profiling in Java
Explains how to use Pyroscope for continuous profiling in Java applications, helping developers analyze CPU and memory usage patterns to improve performance and identify bottlenecks.
https://macronepal.com/blog/pyroscope-profiling-in-java/

OpenTelemetry Metrics in Java: Comprehensive Guide
Provides a complete guide to collecting and exporting metrics in Java using OpenTelemetry, including counters, histograms, gauges, and integration with monitoring tools. (MACRO NEPAL)
https://macronepal.com/blog/opentelemetry-metrics-in-java-comprehensive-guide/

OTLP Exporter in Java: Complete Guide for OpenTelemetry
Explains how to configure OTLP exporters in Java to send telemetry data such as traces, metrics, and logs to monitoring systems using HTTP or gRPC protocols. (MACRO NEPAL)
https://macronepal.com/blog/otlp-exporter-in-java-complete-guide-for-opentelemetry/

Thanos Integration in Java: Global View of Metrics
Explains how to integrate Thanos with Java monitoring systems to create a scalable global metrics view across multiple Prometheus instances.

https://macronepal.com/blog/thanos-integration-in-java-global-view-of-metrics

Time Series with InfluxDB in Java: Complete Guide (Version 2)
Explains how to manage time-series data using InfluxDB in Java applications, including storing, querying, and analyzing metrics data.

https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide-2

Time Series with InfluxDB in Java: Complete Guide
Provides an overview of integrating InfluxDB with Java for time-series data handling, including monitoring applications and managing performance metrics.

https://macronepal.com/blog/time-series-with-influxdb-in-java-complete-guide

Implementing Prometheus Remote Write in Java (Version 2)
Explains how to configure Java applications to send metrics data to Prometheus-compatible systems using the remote write feature for scalable monitoring.

https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide-2

Implementing Prometheus Remote Write in Java: Complete Guide
Provides instructions for sending metrics from Java services to Prometheus servers, enabling centralized monitoring and real-time analytics.

https://macronepal.com/blog/implementing-prometheus-remote-write-in-java-a-complete-guide

Building a TileServer GL in Java: Vector and Raster Tile Server
Explains how to build a TileServer GL in Java for serving vector and raster map tiles, useful for geographic visualization and mapping applications.

https://macronepal.com/blog/building-a-tileserver-gl-in-java-vector-and-raster-tile-server

Indoor Mapping in Java
Explains how to create indoor mapping systems in Java, including navigation inside buildings, spatial data handling, and visualization techniques.

Leave a Reply

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


Macro Nepal Helper