We use cookies to understand how people use Depot.
Container Builds

Optimal Dockerfile for Java with Gradle

Below is an example Dockerfile that we recommend at Depot for building images for Java applications with Gradle.

# syntax=docker/dockerfile:1

FROM eclipse-temurin:21-jdk AS build

ENV GRADLE_HOME=/opt/gradle \
    GRADLE_USER_HOME=/cache/.gradle \
    GRADLE_OPTS="-Dorg.gradle.daemon=false \
    -Dorg.gradle.parallel=true \
    -Dorg.gradle.caching=true \
    -Xmx2g"

ARG GRADLE_VERSION=8.10
RUN apt-get update && apt-get install -y --no-install-recommends unzip \
    && wget -q https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip \
    && unzip gradle-${GRADLE_VERSION}-bin.zip -d /opt \
    && ln -s /opt/gradle-${GRADLE_VERSION} /opt/gradle \
    && rm gradle-${GRADLE_VERSION}-bin.zip \
    && apt-get remove -y unzip \
    && rm -rf /var/lib/apt/lists/*

ENV PATH="${GRADLE_HOME}/bin:${PATH}"

WORKDIR /app

COPY build.gradle ./

RUN --mount=type=cache,target=/cache/.gradle \
    gradle dependencies --no-daemon --stacktrace

COPY src/ src/

RUN --mount=type=cache,target=/cache/.gradle \
    gradle build -x test --no-daemon --stacktrace --build-cache

FROM eclipse-temurin:21-jre AS runtime

RUN groupadd -g 1001 appgroup && \
    useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser

WORKDIR /app

COPY --from=build --chown=appuser:appgroup /app/build/libs/*.jar app.jar

ENV JAVA_OPTS="-server \
    -XX:+UseContainerSupport \
    -XX:MaxRAMPercentage=75.0 \
    -XX:+UseG1GC \
    -Djava.security.egd=file:/dev/./urandom"

USER appuser

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

Explanation of the Dockerfile

At a high level, here are the things we're optimizing in our Docker build for a Java application with Gradle:

  • Multi-stage builds for smaller final images
  • Gradle cache mounts for dependency and build caching
  • Gradle build optimizations for container environments
  • Security optimizations with non-root users

Stage 1: FROM eclipse-temurin:21-jdk AS build

FROM eclipse-temurin:21-jdk AS build

ENV GRADLE_HOME=/opt/gradle \
    GRADLE_USER_HOME=/cache/.gradle \
    GRADLE_OPTS="-Dorg.gradle.daemon=false \
    -Dorg.gradle.parallel=true \
    -Dorg.gradle.caching=true \
    -Xmx2g"

We use Eclipse Temurin 21 JDK and configure Gradle with optimized settings:

  • GRADLE_USER_HOME=/cache/.gradle points to our cache mount location
  • gradle.daemon=false disables the daemon (not beneficial in containers)
  • gradle.parallel=true enables parallel execution for faster builds
  • gradle.caching=true enables Gradle's build cache
  • -Xmx2g sets maximum heap size for Gradle

Installing Gradle

ARG GRADLE_VERSION=8.10
RUN apt-get update && apt-get install -y --no-install-recommends unzip \
    && wget -q https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip \
    && unzip gradle-${GRADLE_VERSION}-bin.zip -d /opt \
    && ln -s /opt/gradle-${GRADLE_VERSION} /opt/gradle \
    && rm gradle-${GRADLE_VERSION}-bin.zip \
    && apt-get remove -y unzip \
    && rm -rf /var/lib/apt/lists/*

ENV PATH="${GRADLE_HOME}/bin:${PATH}"

We install a specific Gradle version for reproducible builds and clean up build tools afterward to keep the layer small.

Dependency resolution and caching

WORKDIR /app

COPY build.gradle ./

RUN --mount=type=cache,target=/cache/.gradle \
    gradle dependencies --no-daemon --stacktrace

We copy only the build.gradle first to leverage Docker layer caching. The dependencies task downloads all dependencies, with a cache mount to persist between builds.

Building the application

COPY src/ src/

RUN --mount=type=cache,target=/cache/.gradle \
    gradle build -x test --no-daemon --stacktrace --build-cache

After copying the source code, we build the application with the same cache mount. Key options:

  • -x test excludes tests from the build (run in CI/CD pipeline)
  • --no-daemon ensures no daemon process is left running
  • --build-cache enables Gradle's build cache for faster incremental builds

Stage 2: FROM eclipse-temurin:21-jre AS runtime

FROM eclipse-temurin:21-jre AS runtime

RUN groupadd -g 1001 appgroup && \
    useradd -u 1001 -g appgroup -m -d /app -s /bin/false appuser

WORKDIR /app

COPY --from=build --chown=appuser:appgroup /app/build/libs/*.jar app.jar

ENV JAVA_OPTS="-server \
    -XX:+UseContainerSupport \
    -XX:MaxRAMPercentage=75.0 \
    -XX:+UseG1GC \
    -Djava.security.egd=file:/dev/./urandom"

USER appuser

ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

The runtime stage uses Eclipse Temurin 21 JRE for a reliable runtime environment. We create a non-root user for security and copy the built JAR file. The JVM is configured with production settings:

  • -server enables server mode for better long-running performance
  • UseContainerSupport and MaxRAMPercentage for container-aware memory management
  • UseG1GC enables the G1 garbage collector for better performance
  • java.security.egd uses /dev/urandom for faster startup

Understanding BuildKit Cache Mounts

Cache mounts are one of the most powerful features for optimizing Docker builds with Depot. This Dockerfile uses the following cache mount syntax:

RUN --mount=type=cache,target=/cache/.gradle \
    gradle dependencies --no-daemon --stacktrace

Cache Mount Parameters Explained

  • type=cache: Specifies this is a cache mount that persists across builds.
  • target=/cache/.gradle: The mount point for Gradle's cache directory (configured via GRADLE_USER_HOME).

For more information regarding Gradle cache mounts, please visit the official Gradle documentation.