Last active
October 18, 2025 08:33
-
-
Save ammbra/59aa7cdb776145a227469730020aa5a4 to your computer and use it in GitHub Desktop.
Example Dockerfiles with Two-Step AOT Workflow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| version: '3.8' | |
| services: | |
| aotgen: | |
| build: | |
| context: . | |
| dockerfile: Dockerfile.aotgen | |
| args: | |
| JAR_FILE: app.jar | |
| environment: | |
| AOT_DIR: /cache | |
| volumes: | |
| - ./cache:/cache | |
| # run once to produce ./cache/app.aot and exit | |
| restart: "no" | |
| petclinic: | |
| build: | |
| context: . | |
| dockerfile: Dockerfile.deploy | |
| environment: | |
| AOT_DIR: /cache | |
| volumes: | |
| - ./cache:/cache | |
| depends_on: | |
| aotgen: | |
| condition: service_completed_successfully | |
| ports: | |
| - "8080" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FROM container-registry.oracle.com/java/openjdk:25-oraclelinux9 as builder | |
| ARG MODULES=java.base,java.compiler,java.desktop,java.instrument,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.sql.rowset,jdk.jfr,jdk.management,jdk.management.agent,jdk.management.jfr,jdk.jcmd,jdk.net,jdk.unsupported | |
| RUN $JAVA_HOME/bin/jlink \ | |
| --add-modules ${MODULES} \ | |
| --no-man-pages \ | |
| --no-header-files \ | |
| --compress=zip-9 \ | |
| --output /javaruntime | |
| WORKDIR /builder | |
| ARG JAR_FILE=target/*.jar | |
| COPY ${JAR_FILE} app.jar | |
| RUN $JAVA_HOME/bin/java -Djarmode=tools -jar app.jar extract --layers --destination extracted | |
| # AOT generator image | |
| FROM container-registry.oracle.com/os/oraclelinux:9-slim | |
| ENV JAVA_HOME=/usr/java/openjdk-25 | |
| ENV PATH=$JAVA_HOME/bin:$PATH | |
| ENV AOT_DIR=/cache | |
| ENV AOT_CACHE=${AOT_DIR}/app.aot | |
| COPY --from=builder /javaruntime $JAVA_HOME | |
| WORKDIR /application | |
| COPY --from=builder /builder/extracted/dependencies/ ./ | |
| COPY --from=builder /builder/extracted/spring-boot-loader/ ./ | |
| COPY --from=builder /builder/extracted/snapshot-dependencies/ ./ | |
| COPY --from=builder /builder/extracted/application/ ./ | |
| # Continue with training run and assembly phase | |
| RUN groupadd -r appuser && useradd -r -g appuser appuser && chown -R appuser:appuser /application | |
| USER appuser | |
| # Run once to generate the AOT cache into the mounted host directory, then exit | |
| CMD ["/bin/sh","-c","mkdir -p \"${AOT_DIR}\" && java -XX:AOTCacheOutput=\"${AOT_CACHE}\" -Dspring.context.exit=onRefresh -jar app.jar"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FROM container-registry.oracle.com/java/openjdk:25-oraclelinux9 as runtime-build | |
| ARG MODULES=java.base,java.compiler,java.desktop,java.instrument,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.sql.rowset,jdk.jfr,jdk.management,jdk.management.agent,jdk.management.jfr,jdk.jcmd,jdk.net,jdk.unsupported | |
| RUN $JAVA_HOME/bin/jlink \ | |
| --add-modules ${MODULES} \ | |
| --no-man-pages \ | |
| --no-header-files \ | |
| --compress=zip-9 \ | |
| --output /javaruntime | |
| FROM container-registry.oracle.com/os/oraclelinux:9-slim | |
| ENV JAVA_HOME /usr/java/openjdk-25 | |
| ENV PATH $JAVA_HOME/bin:$PATH | |
| COPY --from=runtime-build /javaruntime $JAVA_HOME | |
| ARG JAR_FILE=target/*.jar | |
| ENV AOT_DIR=cache | |
| COPY ${JAR_FILE} app.jar | |
| # Continue with training run and assembly phase | |
| RUN mkdir ${AOT_DIR} && chmod 755 ${AOT_DIR} \ | |
| && java -XX:AOTCacheOutput=${AOT_DIR}/app.aot -Dspring.context.exit=onRefresh -jar app.jar \ | |
| && groupadd -r appuser && useradd -r -g appuser appuser | |
| USER appuser | |
| # Deployment run | |
| CMD java -XX:AOTCache=${AOT_DIR} -jar app.jar |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FROM container-registry.oracle.com/java/openjdk:25-oraclelinux9 as builder | |
| ARG MODULES=java.base,java.compiler,java.desktop,java.instrument,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.sql.rowset,jdk.jfr,jdk.management,jdk.management.agent,jdk.management.jfr,jdk.jcmd,jdk.net,jdk.unsupported | |
| RUN $JAVA_HOME/bin/jlink \ | |
| --add-modules ${MODULES} \ | |
| --no-man-pages \ | |
| --no-header-files \ | |
| --compress=zip-9 \ | |
| --output /javaruntime | |
| WORKDIR /builder | |
| ARG JAR_FILE=target/*.jar | |
| COPY ${JAR_FILE} app.jar | |
| RUN $JAVA_HOME/bin/java -Djarmode=tools -jar app.jar extract --layers --destination extracted | |
| # Runtime image | |
| FROM container-registry.oracle.com/os/oraclelinux:9-slim | |
| ENV JAVA_HOME=/usr/java/openjdk-25 | |
| ENV PATH=$JAVA_HOME/bin:$PATH | |
| ENV AOT_DIR=/cache | |
| ENV AOT_CACHE=${AOT_DIR}/app.aot | |
| COPY --from=builder /javaruntime $JAVA_HOME | |
| WORKDIR /application | |
| COPY --from=builder /builder/extracted/dependencies/ ./ | |
| COPY --from=builder /builder/extracted/spring-boot-loader/ ./ | |
| COPY --from=builder /builder/extracted/snapshot-dependencies/ ./ | |
| COPY --from=builder /builder/extracted/application/ ./ | |
| # Non-root runtime | |
| RUN groupadd -r appuser && useradd -r -g appuser appuser && chown -R appuser:appuser /application | |
| USER appuser | |
| CMD java -XX:AOTCache="${AOT_CACHE}" -jar app.jar |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FROM container-registry.oracle.com/java/openjdk:25-oraclelinux9 as builder | |
| ARG MODULES=java.base,java.compiler,java.desktop,java.instrument,java.net.http,java.prefs,java.rmi,java.scripting,java.security.jgss,java.sql.rowset,jdk.jfr,jdk.management,jdk.management.agent,jdk.management.jfr,jdk.jcmd,jdk.net,jdk.unsupported | |
| RUN $JAVA_HOME/bin/jlink \ | |
| --add-modules ${MODULES} \ | |
| --no-man-pages \ | |
| --no-header-files \ | |
| --compress=zip-9 \ | |
| --output /javaruntime | |
| WORKDIR /builder | |
| # This points to the built jar file in the target folder | |
| # Adjust this to 'build/libs/*.jar' if you're using Gradle | |
| ARG JAR_FILE=target/*.jar | |
| # Copy the jar file to the working directory and rename it to application.jar | |
| COPY ${JAR_FILE} app.jar | |
| # Extract the jar file using an efficient layout | |
| RUN $JAVA_HOME/bin/java -Djarmode=tools -jar app.jar extract --layers --destination extracted | |
| FROM container-registry.oracle.com/os/oraclelinux:9-slim | |
| ENV JAVA_HOME /usr/java/openjdk-25 | |
| ENV PATH $JAVA_HOME/bin:$PATH | |
| ENV AOT_DIR=/cache | |
| COPY --from=builder /javaruntime $JAVA_HOME | |
| WORKDIR /application | |
| # Copy the extracted jar contents from the builder container into the working directory in the runtime container | |
| # Every copy step creates a new docker layer | |
| # This allows docker to only pull the changes it really needs | |
| COPY --from=builder /builder/extracted/dependencies/ ./ | |
| COPY --from=builder /builder/extracted/spring-boot-loader/ ./ | |
| COPY --from=builder /builder/extracted/snapshot-dependencies/ ./ | |
| COPY --from=builder /builder/extracted/application/ ./ | |
| # Continue with training run and assembly phase | |
| RUN mkdir ${AOT_DIR} && chmod 755 ${AOT_DIR} \ | |
| && java -XX:AOTCacheOutput=${AOT_DIR}/app.aot -Dspring.context.exit=onRefresh -jar app.jar \ | |
| && groupadd -r appuser && useradd -r -g appuser appuser | |
| USER appuser | |
| # Deployment run | |
| CMD java -XX:AOTCache=${AOT_DIR}/app.aot -jar app.jar |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Different Dockerfile Configurations for Working with AOT Cache
Below are some Dockerfile configurations tried for working with AOT Cache and Spring Boot based applications.
Note
If you build your
.jarwithout enabling layering, you may remove the extra copy commands fordependencies,spring-boot-loaderandsnapshot-dependencies.Basic Dockerfile with JDK Tools Only
The Dockerfile.basic utilizes the following approach:
runtime-buildphase creates a minimal runtime based onjdepsoutput.jdeps --ignore-missing-deps -q --recursive --multi-release 25 --print-module-deps -classpath 'classpath' app.jarAs application dependencies change, I preferred to store the modules in an argument that can be changed at image build time (if needed).
The next Dockerfile stage uses only the operating system as base image thus minimizing the total image size.
It will use the previously built runtime to go through training run + assembly phase and deployment run.
Location of the application jar file is given via
ARG JAR_FILEso it can be overwritten at image build time (docker build -f Dockerfile.basic --build-arg JAR_FILE=/another/path/to/your.jar -t app-basic:local .).Location of the AOT cache is provided via
ENV AOT_CACHEand it can be overwritten at container run time.Commands to build:
docker build -f Dockerfile.basic -t app-basic:local . --no-cacheCommand to run:
Basic Dockerfile with JDK Tools and Layering
The Dockerfile.layer extends the previous approach and add layering via
-Djarmode=tools:The
builderphase creates a minimal runtime but also extracts the layers from the .jar file.The next Dockerfile stage uses only the operating system as base image thus minimizing the total image size.
It will copy the previously built runtime, but also the artifacts from
/builderdirectory.Location of the AOT cache is provided via
ENV AOT_CACHEand it can be overwritten at container run time.Commands to build:
docker build -f Dockerfile.layer -t app-layered:local . --no-cacheCommand to run:
Observed startup time:
Separate Dockerfiles for AOT Generation and Run
Commands to build and generate the AOT cache:
Command to run the application with the AOT cache:
Or simply run
docker-compose up -f compose.yml --buildto achieve the same result.