Repository navigation
Add Dockerfiles: standard JVM, Java 25 AOT, and GraalVM native - #13
Merged
Merged
Conversation
- Dockerfile: Maven wrapper build on eclipse-temurin:25-jdk, layered Spring Boot jar on eclipse-temurin:25-jre, non-root user - Dockerfile.aot: same jar with Spring AOT (spring-boot:process-aot) and a Java 25 AOT cache created by a training run on the runtime image (-XX:AOTCacheOutput / -XX:AOTCache); starts in ~1.4 s instead of ~5.4 s - Dockerfile.native: GraalVM native-image-community:25 builder, native executable on debian:bookworm-slim - .dockerignore, docs/docker.md (native measurements to follow) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
Native image: ~400 MB, starts in ~0.27 s, ~90 MB after start, ~6 min build peaking at ~10.6 GB. All three images verified: API, X-Trace-Id, Swagger UI, async sync (202), non-root user. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
spring.aot.enabled must be a JVM system property (an environment variable is ignored and Spring silently starts without AOT); keep the runtime flags when overriding the entrypoint; heap limits and another GC keep using the AOT cache (checked with -XX:AOTMode=on); the training run needs what the application needs at startup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
Explains why the training run needs dedicated settings when the application connects to a database, Redis or RabbitMQ at startup, shows a value-only training profile and the alternative of training against real services. Notes that bean-switching properties are ignored with spring.aot.enabled (checked: springdoc.api-docs.enabled=false at runtime had no effect) while profile property files still load (checked). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three multi-stage Dockerfiles, documented in
docs/docker.mdand linked from the README. In all three, the application is compiled inside Docker with the Maven wrapper, and the final image runs as a non-root user on port 8080.DockerfileDockerfile.aotDockerfile.nativeeclipse-temurin:25-jdk→eclipse-temurin:25-jreghcr.io/graalvm/native-image-community:25→debian:bookworm-slimDockerfile: Maven dependencies get their own layer and a BuildKit cache mount. The jar is split into Spring Boot's layers and run withjava -jar.Dockerfile.aot: Spring AOT (spring-boot:process-aot, run with-Dspring.aot.enabled=true) plus a Java 25 AOT cache (JEP 483/514/515).-XX:AOTCacheOutputand exits as soon as its context is ready (-Dspring.context.exit=onRefresh).-XX:AOTCache. The cache accounts for most of the speed-up: Spring AOT alone starts in about 4.3 s.-XX:AOTMode=onrun, which refuses to start if the cache can't be used, starts fine.nativeprofile, so it needs no GraalVM-only downloads.Dockerfile.native: builds the native executable in GraalVM's community image and runs it ondebian:bookworm-slim.How this was tested
Each image was built, started three times (startup is the Spring Boot "Started … in" time), and called:
GET /api/v2/projects/1→ 200 withX-Trace-Id, Swagger UI → 200, and on the native imagePOST …/sync→ 202. Each runs asspring.Dockerfile.native, with the official image (after merge). I built it unchanged against the officialghcr.io/graalvm/native-image-community:25(digestsha256:0d936f32…; Oracle Linux 10.1, glibc 2.39, GraalVM CE 25.0.2) and ran the checks above.debian:bookworm-slim(2.36).--network hostand the proxy build args).DockerfileandDockerfile.aot. I tested with the official Temurin images from Google's Docker Hub mirror (mirror.gcr.io), because Docker Hub rate-limited this sandbox. For the build only, the JDK image had this sandbox's proxy certificate and settings added. The runtime images were used unmodified.None of the test setup is in the Dockerfiles.
Test plan
Dockerfile.nativewith the official GraalVM image🤖 Generated with Claude Code
https://claude.ai/code/session_012MLyPXGHeCc1fvTeB8JnT2