d8bcdce879
commit 0d624137c2557c6eeb87020749e4977b821c2b5c Author: Adrien <adrien.cesaro@proton.me> Date: Thu Apr 9 11:55:22 2026 +0200 backend native image setup
263 lines
9.0 KiB
Markdown
263 lines
9.0 KiB
Markdown
# Research: Native Image Deployment
|
|
|
|
**Branch**: `005-native-image-deployment` | **Date**: 2026-04-07
|
|
|
|
## Decision Log
|
|
|
|
---
|
|
|
|
### 1. GraalVM Version and Java 25 Support
|
|
|
|
**Decision**: Use GraalVM JDK 25 (Oracle GraalVM or GraalVM CE 25).
|
|
|
|
**Rationale**: Oracle GraalVM 25 ships with Java 25 support and includes the
|
|
`native-image` tool. GraalVM CE 25 is available via sdkman (`sdk install java 25-graalce`).
|
|
Both support the `native-maven-plugin` 0.10.x workflow.
|
|
|
|
**Alternatives considered**:
|
|
- GraalVM 21 with Java 21: would require downgrading `java.version` in pom.xml — rejected
|
|
because pom.xml already targets Java 25.
|
|
- Mandrel (Red Hat distribution): Red Hat Mandrel 25 may lag behind Oracle GraalVM 25 release;
|
|
acceptable fallback but Oracle GraalVM 25 is preferred.
|
|
|
|
---
|
|
|
|
### 2. Native Image Build Toolchain
|
|
|
|
**Decision**: Use `native-maven-plugin` (0.10.x) inside a Maven `native` profile, combined
|
|
with Spring Boot's AOT processing via `spring-boot-maven-plugin` `process-aot` execution.
|
|
|
|
**Rationale**: This is the canonical Spring Boot 4.x native image approach:
|
|
1. `mvn -Pnative package` runs AOT → generates source under `target/spring-aot/`
|
|
2. `native:compile` compiles the AOT-generated sources + app into a native executable.
|
|
|
|
The `native-maven-plugin` coordinates with GraalVM `native-image` CLI automatically.
|
|
|
|
**Alternatives considered**:
|
|
- Paketo Buildpacks (`spring-boot:build-image -Pnative`): does not use Jib; rejected because
|
|
the user explicitly wants Jib.
|
|
- Manual `native-image` CLI: fragile, no Maven lifecycle integration — rejected.
|
|
|
|
---
|
|
|
|
### 3. Jib + Native Image Integration
|
|
|
|
**Decision**: Use `jib-maven-plugin` (3.4.x) with the `jib-native-image-extension-maven`
|
|
extension (0.1.0).
|
|
|
|
**Rationale**: The Jib native image extension packages a pre-built native executable into a
|
|
Docker image without requiring a Docker daemon. The workflow is:
|
|
|
|
```
|
|
mvn -Pnative native:compile # step 1: build native executable
|
|
mvn -Pnative jib:dockerBuild # step 2: package into Docker image
|
|
```
|
|
|
|
Or combined (with `process-aot` wired in `native` profile):
|
|
|
|
```
|
|
mvn -Pnative package jib:dockerBuild
|
|
```
|
|
|
|
Jib uses the extension to locate the native executable under `target/` and set it as the
|
|
container entrypoint.
|
|
|
|
**Key configuration**:
|
|
```xml
|
|
<plugin>
|
|
<groupId>com.google.cloud.tools</groupId>
|
|
<artifactId>jib-maven-plugin</artifactId>
|
|
<version>3.4.5</version>
|
|
<configuration>
|
|
<from>
|
|
<!-- distroless for security; or cgr.dev/chainguard/static for even smaller -->
|
|
<image>gcr.io/distroless/base-nossl-debian12</image>
|
|
</from>
|
|
<pluginExtensions>
|
|
<pluginExtension>
|
|
<implementation>
|
|
com.google.cloud.tools.jib.maven.extension.nativeimage.JibNativeImageExtension
|
|
</implementation>
|
|
</pluginExtension>
|
|
</pluginExtensions>
|
|
</configuration>
|
|
<dependencies>
|
|
<dependency>
|
|
<groupId>com.google.cloud.tools</groupId>
|
|
<artifactId>jib-native-image-extension-maven</artifactId>
|
|
<version>0.1.0</version>
|
|
</dependency>
|
|
</dependencies>
|
|
</plugin>
|
|
```
|
|
|
|
**Alternatives considered**:
|
|
- Custom Dockerfile for native image: more control but manual, diverges from Jib workflow —
|
|
rejected in favor of Jib as specified.
|
|
- `jib:build` (push to registry) vs `jib:dockerBuild` (local daemon): default to
|
|
`jib:dockerBuild` for dev; `jib:build` for CI with registry configured.
|
|
|
|
---
|
|
|
|
### 4. Docker Base Image for Native Executable
|
|
|
|
**Decision**: `gcr.io/distroless/base-nossl-debian12` (Debian 12 glibc variant).
|
|
|
|
**Rationale**: GraalVM native images compiled on Linux link against glibc by default.
|
|
Distroless provides glibc without a shell, minimizing attack surface.
|
|
|
|
Important: the native executable compiled on the host must target the same glibc ABI as the
|
|
base image. Since the build is on Linux x86_64 this matches Debian 12's glibc 2.36.
|
|
|
|
**Alternatives considered**:
|
|
- `alpine` (musl libc): requires `--static` or `--libc=musl` native-image flag; more complex —
|
|
rejected for simplicity (KISS).
|
|
- `gcr.io/distroless/java-base`: includes JVM libs we don't need — rejected (pure native).
|
|
- `scratch`: too minimal; requires fully static binary with all libs included — rejected.
|
|
|
|
---
|
|
|
|
### 5. Spring AI 2.0.0-M4 Native Image Compatibility
|
|
|
|
**Decision**: Rely on Spring AI's built-in AOT hints; add manual `RuntimeHints` only for gaps
|
|
found during build.
|
|
|
|
**Rationale**: Spring AI 2.0.0-M4 includes `@NativeHint` registrations for the OpenAI
|
|
integration (HTTP client, response DTOs) and pgvector store. Spring Boot's AOT processor
|
|
picks these up automatically. M4 milestone has known native image improvements.
|
|
|
|
**Known gaps** (require manual RuntimeHints):
|
|
- `spring-ai-pdf-document-reader`: PDFBox font/resource loading uses `Class.forName()` and
|
|
classpath resource scanning. Need to register font resources and PDFBox parser classes.
|
|
- `spring-ai-advisors-vector-store`: generally AOT-compatible via Spring AI hints.
|
|
|
|
**Approach**: Add a `NativeHintsConfig` class annotated with `@ImportRuntimeHints` that
|
|
registers PDFBox fonts and any other gaps found during a `-Ob` (quick) native build.
|
|
|
|
---
|
|
|
|
### 6. PDFBox Reflection Hints
|
|
|
|
**Decision**: Register PDFBox font resources and parser classes via a `RuntimeHintsRegistrar`.
|
|
|
|
**Rationale**: PDFBox 3.x loads fonts from classpath resources (`/org/apache/pdfbox/resources/`)
|
|
and uses reflection for CMap/encoding classes. Without hints the native build fails at runtime
|
|
with `ClassNotFoundException` or missing resource errors.
|
|
|
|
**Minimum hints needed**:
|
|
```java
|
|
hints.resources().registerPattern("org/apache/pdfbox/resources/**");
|
|
hints.reflection().registerType(org.apache.pdfbox.pdmodel.font.encoding.GlyphList.class,
|
|
MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
|
|
// Additional classes as discovered during smoke tests
|
|
```
|
|
|
|
---
|
|
|
|
### 7. AWS SDK v2 Native Image Support
|
|
|
|
**Decision**: Add `software.amazon.awssdk:aws-crt-client` is NOT needed; instead add
|
|
`software.amazon.awssdk:netty-nio-client` or use URL connection client.
|
|
Add AWS SDK v2 native image support via `aws-sdk-java-v2-native-bridge` if available,
|
|
otherwise register reflection manually.
|
|
|
|
**Rationale**: AWS SDK v2 2.30.x provides native image support via:
|
|
- `software.amazon.awssdk:apache-client` or `url-connection-client` (simpler, fewer reflection
|
|
needs than netty)
|
|
- The SDK's `SdkPojo` interfaces generate reflection entries; Spring Boot AOT may not cover all.
|
|
|
|
**Minimum approach**: Switch S3 HTTP client from default (netty) to `url-connection-client`
|
|
for simplicity in native mode, register `SdkPojo` subtypes via `RuntimeHints`.
|
|
|
|
**Alternative**: If S3 is used infrequently and only for figure uploads, mark S3Client as a
|
|
conditional bean initialized lazily — reflection issues then surface only on first use (not
|
|
startup), making native hints easier to discover iteratively.
|
|
|
|
---
|
|
|
|
### 8. Flyway in Native Mode
|
|
|
|
**Decision**: No special configuration needed; Flyway 10.x has native image support.
|
|
|
|
**Rationale**: Spring Boot 4.x AOT includes Flyway native hints. PostgreSQL JDBC driver
|
|
and Flyway 10 have been tested with GraalVM native. No manual hints required.
|
|
|
|
---
|
|
|
|
### 9. Spring Security in Native Mode
|
|
|
|
**Decision**: No special configuration needed.
|
|
|
|
**Rationale**: Spring Security 7.x (bundled with Spring Boot 4.x) includes AOT-compatible
|
|
configuration. HTTP Basic auth does not require dynamic proxies at runtime.
|
|
|
|
---
|
|
|
|
### 10. Build Command Summary
|
|
|
|
```bash
|
|
# Install GraalVM 25
|
|
sdk install java 25-graalce
|
|
|
|
# Build native executable + Docker image (local daemon)
|
|
cd backend
|
|
mvn -Pnative package jib:dockerBuild
|
|
|
|
# Or push to registry (CI)
|
|
mvn -Pnative package jib:build \
|
|
-Djib.to.image=ghcr.io/your-org/ai-teacher-backend:native-latest \
|
|
-Djib.to.auth.username=$CI_USER \
|
|
-Djib.to.auth.password=$CI_TOKEN
|
|
```
|
|
|
|
---
|
|
|
|
### 11. Maven Profile Structure
|
|
|
|
```xml
|
|
<profiles>
|
|
<profile>
|
|
<id>native</id>
|
|
<build>
|
|
<plugins>
|
|
<!-- AOT processing -->
|
|
<plugin>
|
|
<groupId>org.springframework.boot</groupId>
|
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
<executions>
|
|
<execution>
|
|
<id>process-aot</id>
|
|
<goals><goal>process-aot</goal></goals>
|
|
</execution>
|
|
</executions>
|
|
</plugin>
|
|
<!-- Native compile -->
|
|
<plugin>
|
|
<groupId>org.graalvm.buildtools</groupId>
|
|
<artifactId>native-maven-plugin</artifactId>
|
|
<version>0.10.6</version>
|
|
<executions>
|
|
<execution>
|
|
<id>add-reachability-metadata</id>
|
|
<goals><goal>add-reachability-metadata</goal></goals>
|
|
</execution>
|
|
<execution>
|
|
<id>compile</id>
|
|
<goals><goal>compile-no-fork</goal></goals>
|
|
<phase>package</phase>
|
|
</execution>
|
|
</executions>
|
|
<configuration>
|
|
<imageName>ai-teacher-backend</imageName>
|
|
<buildArgs>
|
|
<buildArg>--initialize-at-build-time=org.slf4j</buildArg>
|
|
<buildArg>-H:+ReportExceptionStackTraces</buildArg>
|
|
</buildArgs>
|
|
</configuration>
|
|
</plugin>
|
|
</plugins>
|
|
</build>
|
|
</profile>
|
|
</profiles>
|
|
```
|