diff --git a/Dockerfile b/Dockerfile
index 5decd7b..a875659 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM eclipse-temurin:23 AS build
+FROM eclipse-temurin:24 AS build
 
 WORKDIR /build
 
@@ -25,22 +25,52 @@ COPY src src
 
 RUN ./mvnw compile
 
-FROM debian:stable-slim AS runtime
-
-WORKDIR /app
+FROM eclipse-temurin:24 AS training
 
+WORKDIR /build
 COPY --from=build /build/jre jre
 COPY --from=build /build/lib lib
 COPY --from=build /build/classpath classpath
 COPY --from=build /build/target/classes classes
 
-# Adds the output of Maven compilation to output
-RUN echo ":classes" >> classpath
+# There can be no directories on the classpath when training
+RUN cd classes && jar -cf /build/app.jar *
+
+# Adds the new jar to the classpath
+RUN echo ":app.jar" >> classpath
+
+RUN [ "./jre/bin/java" \
+    , "-cp", "@classpath" \
+    , "-XX:AOTMode=record" \
+    , "-XX:AOTConfiguration=app.aotconf" \
+    , "se.su.dsv.oauth2.Training" \
+    , "--spring.profiles.active=dev,embedded" \
+    ]
+
+RUN [ "./jre/bin/java" \
+    , "-cp", "@classpath" \
+    , "-XX:AOTMode=create" \
+    , "-XX:AOTConfiguration=app.aotconf" \
+    , "-XX:AOTCache=app.aot" \
+    , "se.su.dsv.oauth2.Training" \
+    , "--spring.profiles.active=dev,embedded" \
+    ]
+
+FROM debian:stable-slim AS runtime
+
+WORKDIR /app
+
+COPY --from=training /build/jre jre
+COPY --from=training /build/lib lib
+COPY --from=training /build/classpath classpath
+COPY --from=training /build/app.jar app.jar
+COPY --from=training /build/app.aot app.aot
 
 EXPOSE 8080
 
 CMD [ "./jre/bin/java" \
     , "-cp", "@classpath" \
+    , "-XX:AOTCache=app.aot" \
     , "se.su.dsv.oauth2.AuthorizationServer" \
     , "--spring.profiles.active=dev,embedded" \
     ]
diff --git a/src/main/java/se/su/dsv/oauth2/Training.java b/src/main/java/se/su/dsv/oauth2/Training.java
new file mode 100644
index 0000000..3326fe9
--- /dev/null
+++ b/src/main/java/se/su/dsv/oauth2/Training.java
@@ -0,0 +1,15 @@
+package se.su.dsv.oauth2;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public class Training {
+    // Used by the Docker build to do a training run to speed up the startup time of the application.
+    // See https://openjdk.org/jeps/483
+    // Training is very basic and just boots the application and immediately shuts it down.
+    // A better training run would involve going through a full OAuth 2 flow to load all the classes.
+    public static void main(String[] args) {
+        ConfigurableApplicationContext application = SpringApplication.run(AuthorizationServer.class, args);
+        application.close();
+    }
+}