Dockerfile


https://docs.docker.com/build/building/multi-stage/
https://docs.docker.com/build/building/secrets/
https://github.com/adoptium/containers
https://docs.spring.io/spring-boot/reference/packaging/container-images/dockerfiles.html

1. Important Points#

Java container rules:
    build with JDK
    run with JRE when possible
    pass JVM options at runtime
    pass Spring profile at runtime
    do not bake secrets or env files into image

2. Runtime Only Dockerfile#

FROM eclipse-temurin:21-jre

WORKDIR /app
RUN addgroup --system app && adduser --system --ingroup app app

COPY target/order-api.jar /app/app.jar

USER app
EXPOSE 8080

ENV JAVA_TOOL_OPTIONS=""
ENV APP_ARGS=""

ENTRYPOINT ["sh", "-c", "exec java $JAVA_TOOL_OPTIONS -jar /app/app.jar $APP_ARGS"]
docker build -f Dockerfile.runtime -t order-api:runtime .
docker run --rm -p 8080:8080 \
  -e JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -Dfile.encoding=UTF-8" \
  -e APP_ARGS="--spring.profiles.active=prod" \
  order-api:runtime

3. Multi Stage Dockerfile#

# syntax=docker/dockerfile:1

FROM eclipse-temurin:21-jdk AS build
WORKDIR /workspace

COPY .mvn .mvn
COPY mvnw pom.xml ./
RUN ./mvnw -B -q -DskipTests dependency:go-offline

COPY src src
RUN ./mvnw -B clean package -DskipTests

FROM eclipse-temurin:21-jre AS runtime
WORKDIR /app
RUN addgroup --system app && adduser --system --ingroup app app

COPY --from=build /workspace/target/*.jar /app/app.jar

USER app
EXPOSE 8080

ENV JAVA_TOOL_OPTIONS=""
ENV SPRING_PROFILES_ACTIVE=""
ENV APP_ARGS=""

ENTRYPOINT ["sh", "-c", "exec java $JAVA_TOOL_OPTIONS -jar /app/app.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE:-default} $APP_ARGS"]
docker build -t order-api:local .
docker run --rm -p 8080:8080 \
  -e SPRING_PROFILES_ACTIVE=prod \
  -e JAVA_TOOL_OPTIONS="-XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=75" \
  order-api:local

4. Multi Stage With Maven Cache And Private Repo#

# syntax=docker/dockerfile:1

FROM eclipse-temurin:21-jdk AS build
WORKDIR /workspace

COPY .mvn .mvn
COPY mvnw pom.xml settings.xml ./

RUN --mount=type=cache,target=/root/.m2/repository \
    --mount=type=secret,id=maven_settings,target=/root/.m2/settings.xml \
    ./mvnw -B -DskipTests dependency:go-offline

COPY src src

RUN --mount=type=cache,target=/root/.m2/repository \
    --mount=type=secret,id=maven_settings,target=/root/.m2/settings.xml \
    ./mvnw -B clean package -DskipTests

FROM eclipse-temurin:21-jre
WORKDIR /app
RUN addgroup --system app && adduser --system --ingroup app app
COPY --from=build /workspace/target/*.jar /app/app.jar
USER app
EXPOSE 8080
ENV JAVA_TOOL_OPTIONS=""
ENV APP_ARGS=""
ENTRYPOINT ["sh", "-c", "exec java $JAVA_TOOL_OPTIONS -jar /app/app.jar $APP_ARGS"]
docker build \
  --secret id=maven_settings,src="$HOME/.m2/settings.xml" \
  -t order-api:ci .
secret mount:
    settings.xml is available during build step
    it is not copied into final image layer

5. Spring Boot Layered Jar#

FROM eclipse-temurin:21-jdk AS extract
WORKDIR /workspace
COPY target/order-api.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract

FROM eclipse-temurin:21-jre
WORKDIR /app
RUN addgroup --system app && adduser --system --ingroup app app

COPY --from=extract /workspace/dependencies/ ./
COPY --from=extract /workspace/spring-boot-loader/ ./
COPY --from=extract /workspace/snapshot-dependencies/ ./
COPY --from=extract /workspace/application/ ./

USER app
EXPOSE 8080
ENV JAVA_TOOL_OPTIONS=""
ENV SPRING_PROFILES_ACTIVE=""

ENTRYPOINT ["sh", "-c", "exec java $JAVA_TOOL_OPTIONS org.springframework.boot.loader.launch.JarLauncher --spring.profiles.active=${SPRING_PROFILES_ACTIVE:-default}"]
layered jar:
    separates dependencies and application classes
    improves rebuild cache when only application code changes

6. Runtime JVM Options#

recommended env:
    JAVA_TOOL_OPTIONS:
        JVM options recognized automatically by java launcher

    APP_ARGS:
        application arguments after -jar

    SPRING_PROFILES_ACTIVE:
        Spring Boot runtime profile
JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError -Dfile.encoding=UTF-8"
APP_ARGS="--server.port=8080 --logging.level.root=INFO"

7. .dockerignore#

.git
target
*.log
.env
.env.*
.idea
.vscode
do not ignore:
    pom.xml
    mvnw
    .mvn
    src