Files
Adrien d8bcdce879 Squashed commit of the following:
commit 0d624137c2557c6eeb87020749e4977b821c2b5c
Author: Adrien <adrien.cesaro@proton.me>
Date:   Thu Apr 9 11:55:22 2026 +0200

    backend native image setup
2026-04-09 12:05:02 +02:00

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:

  1. Add <profile id="native"> to backend/pom.xml:

    • Wire spring-boot-maven-plugin process-aot goal to prepare-package phase.
    • Add native-maven-plugin 0.10.6 with add-reachability-metadata and compile-no-fork executions.
    • Set <imageName>ai-teacher-backend</imageName> and add essential build args: -H:+ReportExceptionStackTraces, --initialize-at-build-time=org.slf4j.
  2. 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 SdkPojo subtypes used by S3 (discovered iteratively).
    • Annotate main application class or a @Configuration class with @ImportRuntimeHints(NativeHintsConfig.class).
  3. Validate: run mvn -Pnative package -DskipTests and 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:

  1. Add jib-maven-plugin 3.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-maven 0.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 to JibNativeImageExtension.
    • Set <container><ports><port>8080</port></ports></container>.
  2. Verify Jib picks up the native executable from target/ai-teacher-backend and sets it as entrypoint.

  3. Run mvn -Pnative package jib:dockerBuild and confirm:

    • Image exists in local Docker: docker images | grep ai-teacher-backend.
    • Container starts: docker run --rm -e SPRING_DATASOURCE_URL=... ai-teacher-backend.

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:

  1. Start PostgreSQL locally (or via existing docker-compose.yml).

  2. Start the native container with full environment variables.

  3. 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.
  4. Fix any MissingResourceException or ClassNotFoundException by adding entries to NativeHintsConfig and 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:

  1. Create docker-compose.native.yml at repo root:

    • postgres service: same as docker-compose.yml (copy).
    • backend service: uses ai-teacher-backend:latest (the Jib-built native image).
    • Wires all required env vars via .env file reference or inline secrets.
    • Adds depends_on with health check for postgres.
  2. Document the .env file format in quickstart.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:

  1. 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.
  2. 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