Optimize Docker for Java
Analysis of Dockerfile optimization techniques from Dockerfile Contest 2025 for Java Spring Boot applications.
Dockerfile Contest 2025 – Extreme Java Optimization
Dockerfile Contest 2025 encourages the Vietnamese DevOps community to rethink how Dockerfiles are written to achieve security, optimization, and clarity. This article focuses on the Java Spring Boot category, where authors optimize the JRE, reduce image size, and standardize build workflows.
I. JAVA Category (Spring Boot Service)
The Java category focuses on:
- JRE optimization: use
jlinkandjdepsto build a custom JRE that only includes required modules. - Security & dependency optimization: automatically update dependencies to safer versions and reduce CVEs.
- Multi-stage build: separate build and runtime stages, use distroless or optimized base images.
- Clear healthchecks: use
wgetor native Java healthchecks to monitor containers.
Dockerfile TOP 1 (Java) – Spring Boot Template + Distroless
| Technique | Author’s explanation | Reference |
|---|---|---|
| Custom JRE with jlink | Use jdeps to analyze the fat JAR for modules, then jlink builds a JRE with only needed modules, significantly reducing runtime size. | |
| Clear multi-stage build | Build stage uses eclipse-temurin:21-jdk for Gradle; runtime uses gcr.io/distroless/base-debian12 to run only the JRE. | |
| Separate Healthcheck Stage | Create a healthcheck stage with BusyBox, copy wget into runtime to avoid installing curl/wget via a package manager. | |
| Distroless Runtime | Use distroless base to reduce attack surface (no shell, no package manager), smaller and safer image. | |
| Pin source & license | Add LABELs org.opencontainers.image.source, version, licenses for traceability and license compliance. |
Dockerfile TOP 1 (JAVA)
| |
Dockerfile TOP 2 (Java) – Gradle Auto Dependency Update + Custom Healthcheck
| Technique | Author’s explanation | Reference |
|---|---|---|
| Gradle Dependency Updates | Use the dependencyUpdates plugin to generate a report, then parse it to auto-update plugin/ext/dependency versions in build.gradle. | |
| Force Dependency Upgrade | DEPENDENCIES_FORCE_UPDATE lets you specify group:name:version to force upgrades of critical dependencies, usually CVE-related libs. | |
| Native Java Healthcheck | HealthCheck.java uses HttpURLConnection to call /health, avoiding curl/wget dependencies. | |
| Distroless Base Java | Runtime uses hmctspublic.azurecr.io/base/java:21-distroless, optimized Java 21 image for production. | |
| Gradle Cache | Mount cache /root/.gradle to speed up ./gradlew in the build stage. |
Dockerfile TOP 2 (JAVA)
| |
Dockerfile TOP (Java) – Dung Cao (Optimized Alpine JDK/JRE)
| Technique | Author’s explanation | Reference |
|---|---|---|
| Separate JDK/JRE via ARG | Use ARG BUILD_JDK_IMAGE and RUNTIME_IMAGE to switch base images (JDK for build, JRE for runtime) while keeping the Dockerfile simple. | |
| Gradle cache with BuildKit | Use --mount=type=cache,target=/cache/.gradle for ./gradlew to speed up repeat builds. | |
| Skip tests in build image | bootJar -x test speeds builds in CI/CD and contest context. | |
| Non-root user + chown | Create user/group app, use --chown=app:app when copying JAR to keep runtime secure and CIS-compliant. | |
| Healthcheck with curl | Install curl and use curl -fsS to /health for clear, debuggable healthchecks. | |
| JVM tuning for containers | JAVA_OPTS enables UseContainerSupport, MaxRAMPercentage=75, G1GC, ExitOnOutOfMemoryError, heap dump path, helping JVM respect container limits and fail fast on OOM. |
Dockerfile TOP (JAVA) – Dung Cao
| |
Dockerfile TOP (Reference – Java) – HMCTS Spring Boot Template (Layered + jlink)
| Technique | Author’s explanation | Reference |
|---|---|---|
| Gradle cache + layer split | Use BuildKit cache id=gradle-cache for Gradle, then jarmode=layertools to extract layers dependencies, spring-boot-loader, snapshot-dependencies, application for maximum layer caching. | |
| Custom JRE with jlink | Run jlink with selected Java modules to create a minimal JRE (/jre-minimal), reducing >100MB compared to full JDK. | |
| Minimal Alpine runtime | Runtime base alpine:3.21 installs only ca-certificates, tzdata, tini, curl, then runs as non-root appuser. | |
| JAVA_TOOL_OPTIONS for containers | Optimize JVM: UseContainerSupport, MaxRAMPercentage, UseG1GC, UseStringDeduplication, ExitOnOutOfMemoryError, … to optimize memory and GC in containers. | |
| Very complete OCI labels | Include vendor, authors, source, version, revision, base.name, base.digest, com.hmcts.* for traceability. | |
| Healthcheck using curl | Healthcheck /health via curl -f, with reasonable retries for Spring Boot startup. |
Dockerfile TOP (Reference – JAVA) – HMCTS Spring Boot Template
| |
Deployment Notes
- When applying this to your Java project, keep the core principles shown above:
- Clear multi-stage build (builder + runtime).
- JRE optimization (
jdeps+jlink, distroless or JRE-only base images). - Clear healthchecks (native Java or wget/curl).
- Dependency optimization (automatic or manual) to reduce CVEs.
- When copying the template to your own project, you should:
- Update
LABEL org.opencontainers.image.sourceto your repository. - Adjust the healthcheck endpoint (
/health,/actuator/health, …) to match your app. - Build and scan the image with tools like Trivy to verify security after optimization.
- Update
RESPONSES & DISCUSSION
