commit 0d624137c2557c6eeb87020749e4977b821c2b5c Author: Adrien <adrien.cesaro@proton.me> Date: Thu Apr 9 11:55:22 2026 +0200 backend native image setup
9.2 KiB
Implementation Plan: Native Image Deployment
Branch: 005-native-image-deployment | Date: 2026-04-07 | Spec: spec.md
Input: Feature specification from /specs/005-native-image-deployment/spec.md
Summary
Add a native Maven profile to the backend that compiles the Spring Boot 4.0.5 application to a
GraalVM 25 native executable, then packages it into a minimal Docker image via Jib. The JVM build
(default profile + existing Dockerfile) remains unchanged. A companion docker-compose.native.yml
enables local full-stack testing with the native image. README.md is updated with build
instructions and an updated architecture diagram.
Technical Context
Language/Version: Java 25 (backend), TypeScript / Node 20 (frontend)
Primary Dependencies: Spring Boot 4.0.5, Spring AI 2.0.0-M4, native-maven-plugin 0.10.6,
jib-maven-plugin 3.4.5, jib-native-image-extension-maven 0.1.0
Storage: PostgreSQL 16 + pgvector (unchanged)
Testing: Spring Boot Test / JUnit 5 (unchanged); native integration test via smoke-test on
produced Docker image
Target Platform: Linux x86_64 container (Docker), GraalVM 25 CE or Oracle GraalVM 25
Project Type: Web application — backend API + frontend client (Option 2 structure)
Performance Goals: Backend container starts in < 1 s; idle RSS < 150 MB
Constraints: Native profile is opt-in; JVM mode unchanged; no new runtime dependencies;
cross-compilation out of scope
Scale/Scope: Single backend deployable unit (1 Docker image); frontend unchanged
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle | Status | Notes |
|---|---|---|
| I. KISS | ✅ Pass | Build-time change only; no new runtime layers or abstractions. |
| II. Easy to Change | ✅ Pass | native profile is additive; default JVM profile unchanged. Native hints isolated in one NativeHintsConfig class. |
| III. Web-First Architecture | ✅ Pass | REST API contract unchanged. |
| IV. Documentation as Architecture | ⚠️ Required | README.md MUST be updated with native build instructions and updated Mermaid diagram showing the build pipeline. |
| Technology Constraints | ✅ Pass | Still 1 backend + 1 frontend deployable unit; no new services. |
Post-design re-check: After Phase 1 — Principle IV gate requires README update in same PR.
Project Structure
Documentation (this feature)
specs/005-native-image-deployment/
├── plan.md # This file
├── spec.md # Feature specification
├── research.md # Phase 0 — toolchain & compatibility decisions
├── data-model.md # Phase 1 — no new entities (confirmed)
├── quickstart.md # Phase 1 — developer build guide
├── contracts/
│ └── build-contract.md # Build inputs/outputs & unchanged REST API
└── tasks.md # Phase 2 output (/speckit.tasks — NOT created here)
Source Code (repository root)
backend/
├── pom.xml # +native profile, +Jib plugin, +NativeHintsConfig
├── src/
│ └── main/
│ └── java/com/aiteacher/
│ └── config/
│ └── NativeHintsConfig.java # NEW — RuntimeHints for PDFBox, AWS SDK gaps
└── src/
└── main/
└── resources/
└── META-INF/
└── native-image/ # Optional: manual JSON hints if AOT gaps remain
docker-compose.yml # UNCHANGED (JVM mode)
docker-compose.native.yml # NEW — native image + postgres full stack
README.md # UPDATED — build instructions + Mermaid diagram
Structure Decision: Follows Option 2 (web application). Only backend is modified; frontend is untouched. New files are minimal: one Java config class, one docker-compose file.
Complexity Tracking
No constitution violations. No entry required.
Implementation Phases
Phase 1: Maven native Profile + Native Maven Plugin
Goal: mvn -Pnative package produces a native executable target/ai-teacher-backend.
Tasks:
-
Add
<profile id="native">tobackend/pom.xml:- Wire
spring-boot-maven-pluginprocess-aotgoal toprepare-packagephase. - Add
native-maven-plugin0.10.6 withadd-reachability-metadataandcompile-no-forkexecutions. - Set
<imageName>ai-teacher-backend</imageName>and add essential build args:-H:+ReportExceptionStackTraces,--initialize-at-build-time=org.slf4j.
- Wire
-
Create
backend/src/main/java/com/aiteacher/config/NativeHintsConfig.java:- Implements
RuntimeHintsRegistrar. - Registers PDFBox classpath resources:
org/apache/pdfbox/resources/**. - Registers PDFBox reflection entries for font/encoding classes.
- Registers AWS SDK
SdkPojosubtypes used by S3 (discovered iteratively). - Annotate main application class or a
@Configurationclass with@ImportRuntimeHints(NativeHintsConfig.class).
- Implements
-
Validate: run
mvn -Pnative package -DskipTestsand confirm executable is produced.
Acceptance: target/ai-teacher-backend exists, ./target/ai-teacher-backend --help
exits cleanly, basic startup reaches DB connection stage.
Phase 2: Jib Plugin — Package Native Image into Docker
Goal: mvn -Pnative package jib:dockerBuild produces a local Docker image.
Tasks:
-
Add
jib-maven-plugin3.4.5 to the<build><plugins>section (outside native profile, but configured to package the native executable when the profile is active):- Add
jib-native-image-extension-maven0.1.0 as a plugin dependency. - Configure
<from><image>gcr.io/distroless/base-nossl-debian12</image></from>. - Set
<to><image>ai-teacher-backend</image></to>(overridable via-Djib.to.image). - Add
<pluginExtensions>pointing toJibNativeImageExtension. - Set
<container><ports><port>8080</port></ports></container>.
- Add
-
Verify Jib picks up the native executable from
target/ai-teacher-backendand sets it as entrypoint. -
Run
mvn -Pnative package jib:dockerBuildand confirm:- Image exists in local Docker:
docker images | grep ai-teacher-backend. - Container starts:
docker run --rm -e SPRING_DATASOURCE_URL=... ai-teacher-backend.
- Image exists in local Docker:
Acceptance: Image starts, logs show "Started AiTeacherApplication in < 1.0 seconds",
and GET /api/v1/books returns 200 (with DB connected).
Phase 3: Integration Smoke Test
Goal: All existing features work in native mode.
Tasks:
-
Start PostgreSQL locally (or via existing docker-compose.yml).
-
Start the native container with full environment variables.
-
Verify:
- Flyway migrations run successfully at startup.
POST /api/v1/books(PDF upload + embedding) succeeds.POST /api/v1/chat(RAG chat) returns a non-empty answer.- Spring Security HTTP Basic auth is enforced.
-
Fix any
MissingResourceExceptionorClassNotFoundExceptionby adding entries toNativeHintsConfigand rebuilding.
Acceptance: All 5 REST API endpoints respond correctly with native image (FR-005).
Phase 4: Docker Compose for Native Stack
Goal: docker compose -f docker-compose.native.yml up starts the full stack.
Tasks:
-
Create
docker-compose.native.ymlat repo root:postgresservice: same asdocker-compose.yml(copy).backendservice: usesai-teacher-backend:latest(the Jib-built native image).- Wires all required env vars via
.envfile reference or inline secrets. - Adds
depends_onwith health check for postgres.
-
Document the
.envfile format inquickstart.md.
Acceptance: docker compose -f docker-compose.native.yml up → all services healthy →
app responds at http://localhost:8080/api/v1/books.
Phase 5: README Update (Constitution IV Gate)
Goal: README.md reflects the new build pipeline and updated deployment diagram.
Tasks:
-
Add "Native Image Build" section to README.md with:
- Prerequisite: GraalVM 25 install instructions (sdkman command).
- Build command:
mvn -Pnative package jib:dockerBuild. - Run command:
docker compose -f docker-compose.native.yml up.
-
Update the Mermaid architecture diagram in README.md to show:
- Build pipeline: Maven native profile → GraalVM native-image → Jib → Docker image.
- Keep the runtime diagram unchanged (same components, just a lighter container).
Acceptance: README.md Mermaid diagram renders correctly on GitHub; build section
contains all commands from quickstart.md.
Risk Log
| Risk | Likelihood | Mitigation |
|---|---|---|
| Spring AI 2.0.0-M4 missing native hints for some AI response types | Medium | Iterative hint discovery during Phase 3 smoke test |
| PDFBox font loading fails at runtime in native | Medium | Register full org/apache/pdfbox/resources/** in NativeHintsConfig |
| AWS SDK S3 reflection errors | Medium | Switch to url-connection-client (simpler); lazy-init S3Client |
| GraalVM 25 availability in CI | Low | Documented prerequisite; CI pipeline config out of scope (can be added as follow-up) |
| Native build time > 5 min | Low | Acceptable for a first build; not a blocker for POC |