diff --git a/README.md b/README.md
index 1f19c51..f35d2b8 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
[](https://github.com/hadolint/hadolint)
[](https://prettier.io/)
-Reference [Dockerfiles](https://docs.docker.com/engine/reference/builder/) and best-practice guides for building **secure container images** that ship to production: small, signed, scanner-friendly, [OCI](https://opencontainers.org/)-compliant. [Python](https://www.python.org/), [Go](https://go.dev/), [JAX](https://jax.readthedocs.io/), [Node.js](https://nodejs.org/), [TypeScript](https://www.typescriptlang.org/), and [Rust](https://www.rust-lang.org/) are covered.
+Reference [Dockerfiles](https://docs.docker.com/engine/reference/builder/) and best-practice guides for building **secure container images** that ship to production: small, signed, scanner-friendly, [OCI](https://opencontainers.org/)-compliant. [Python](https://www.python.org/), [Go](https://go.dev/), [JAX](https://jax.readthedocs.io/), [Node.js](https://nodejs.org/), [TypeScript](https://www.typescriptlang.org/), [Rust](https://www.rust-lang.org/), and [Java](https://openjdk.org/) are covered.
## Why these templates
@@ -135,6 +135,18 @@ Multi-stage [Rust](https://www.rust-lang.org/) images using [cargo-chef](https:/
Full documentation: [`dockerfiles/rust/README.md`](dockerfiles/rust/README.md).
+### Java — Maven/Gradle dep caching + Distroless JRE
+
+Multi-stage [Java](https://openjdk.org/) images supporting both Maven and Gradle via `--build-arg BUILD_TOOL`. Deps are cached in a dedicated layer before source is copied. Ships in [**Google Distroless java21**](https://github.com/GoogleContainerTools/distroless/blob/main/java/README.md) — JRE only, no shell, no package manager. Includes a GraalVM Native Image variant for fast cold starts.
+
+- Distroless JVM image (Maven/Gradle) → [`dockerfiles/java/Dockerfile.java`](dockerfiles/java/Dockerfile.java)
+- GraalVM Native Image → [`dockerfiles/java/Dockerfile.java.native`](dockerfiles/java/Dockerfile.java.native)
+- Chainguard production image → [`dockerfiles/java/Dockerfile.java.chainguard`](dockerfiles/java/Dockerfile.java.chainguard)
+- AWS Lambda container image → [`dockerfiles/java/Dockerfile.lambda`](dockerfiles/java/Dockerfile.lambda)
+- VS Code devcontainer (Java 21 + Maven + Gradle) → [`dockerfiles/java/Dockerfile.devcontainer`](dockerfiles/java/Dockerfile.devcontainer)
+
+Full documentation: [`dockerfiles/java/README.md`](dockerfiles/java/README.md).
+
### Coming soon
Tracked as issues — comment or 👍 to bump priority.
diff --git a/dockerfiles/java/.devcontainer/devcontainer.json b/dockerfiles/java/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..178552f
--- /dev/null
+++ b/dockerfiles/java/.devcontainer/devcontainer.json
@@ -0,0 +1,36 @@
+{
+ "name": "Java",
+ "build": {
+ "dockerfile": "../Dockerfile.devcontainer",
+ "context": "..",
+ "args": {
+ "VARIANT": "1-21-bookworm"
+ }
+ },
+ "remoteUser": "vscode",
+ "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
+ "workspaceFolder": "/workspace",
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "redhat.java",
+ "vscjava.vscode-maven",
+ "vscjava.vscode-gradle",
+ "vscjava.vscode-java-test",
+ "vscjava.vscode-java-debug",
+ "vscjava.vscode-spring-initializr",
+ "vmware.vscode-spring-boot",
+ "usernamehw.errorlens"
+ ],
+ "settings": {
+ "java.configuration.updateBuildConfiguration": "automatic",
+ "java.compile.nullAnalysis.mode": "automatic",
+ "editor.formatOnSave": true,
+ "[java]": {
+ "editor.defaultFormatter": "redhat.java"
+ }
+ }
+ }
+ },
+ "postCreateCommand": "mvn dependency:go-offline -q 2>/dev/null || gradle dependencies -q 2>/dev/null || true"
+}
diff --git a/dockerfiles/java/.dockerignore b/dockerfiles/java/.dockerignore
new file mode 100644
index 0000000..40e18b3
--- /dev/null
+++ b/dockerfiles/java/.dockerignore
@@ -0,0 +1,42 @@
+# Maven build output
+target/
+
+# Gradle build output and cache
+build/
+.gradle/
+
+# Gradle wrapper jar (optional -- include if offline builds are needed)
+gradle/wrapper/gradle-wrapper.jar
+
+# Compiled class files
+*.class
+
+# Stale fat JARs in the source root
+*.jar
+
+# Editor and IDE
+.vscode/
+.idea/
+*.iml
+*.swp
+*.swo
+
+# Devcontainer
+.devcontainer/
+
+# Credentials and secrets
+.env
+*.pem
+*.key
+*.cert
+
+# CI
+.github/
+
+# Git
+.git/
+.gitignore
+
+# Documentation
+*.md
+LICENSE
diff --git a/dockerfiles/java/Dockerfile.devcontainer b/dockerfiles/java/Dockerfile.devcontainer
new file mode 100644
index 0000000..24468a1
--- /dev/null
+++ b/dockerfiles/java/Dockerfile.devcontainer
@@ -0,0 +1,33 @@
+# Java devcontainer image (Microsoft devcontainers/java base).
+#
+# Extends the official VS Code Java devcontainer with additional tooling:
+# - Gradle (pinned) for projects not using the Gradle wrapper
+# - jq and curl for JSON processing and HTTP requests
+#
+# The base image ships: Java JDK, Maven, git, common dev utilities, and
+# the VS Code remote-containers server.
+#
+# Build context: dockerfiles/java/
+# docker build -f Dockerfile.devcontainer .
+#
+# Typical usage: open in VS Code with the Dev Containers extension;
+# the .devcontainer/devcontainer.json references this file.
+ARG VARIANT=1-21-bookworm
+
+FROM mcr.microsoft.com/devcontainers/java:${VARIANT}
+
+USER root
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ jq=1.6-2.1 \
+ curl=7.88.1-10+deb12u12 \
+ && rm -rf /var/lib/apt/lists/*
+
+ARG GRADLE_VERSION=8.11
+RUN curl -fsSL "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
+ -o /tmp/gradle.zip && \
+ unzip -q /tmp/gradle.zip -d /opt && \
+ ln -s "/opt/gradle-${GRADLE_VERSION}/bin/gradle" /usr/local/bin/gradle && \
+ rm /tmp/gradle.zip
+
+USER vscode
diff --git a/dockerfiles/java/Dockerfile.java b/dockerfiles/java/Dockerfile.java
new file mode 100644
index 0000000..6634ec9
--- /dev/null
+++ b/dockerfiles/java/Dockerfile.java
@@ -0,0 +1,77 @@
+# Java application image (Eclipse Temurin + Google Distroless java21, multi-stage).
+#
+# Supports both Maven and Gradle via BUILD_TOOL build-arg (default: maven).
+# Dependency manifests are copied before source, so a source-only change does
+# not invalidate the dependency-download layer.
+#
+# BUILD_TOOL stages:
+# maven-deps — copies pom.xml and resolves deps offline (Maven projects).
+# gradle-deps — copies Gradle wrappers and resolves deps (Gradle projects).
+# builder — FROM ${BUILD_TOOL}-deps; compiles and packages the fat JAR.
+#
+# Only the stage matching BUILD_TOOL is executed by BuildKit — the other is
+# skipped entirely, so the Gradle stage never runs in a Maven project and
+# vice versa.
+#
+# Runtime: gcr.io/distroless/java21-debian12:nonroot — ships the JRE but no
+# shell, no package manager. The process cannot drop into a shell or install
+# tools. Incompatible with GraalVM native image; use Dockerfile.java.native
+# for native compilation.
+#
+# Spring Boot layered JARs:
+# For Spring Boot 2.3+ apps, unpack the fat JAR into layers to get better
+# Docker cache utilisation — each layer (dependencies, spring-boot-loader,
+# snapshot-dependencies, application) is a separate COPY. See README.md.
+#
+# Multi-arch builds:
+# docker buildx build --platform=linux/amd64,linux/arm64 \
+# --build-arg BUILD_TOOL=maven -t myapp -f Dockerfile.java .
+#
+# Build:
+# docker build --build-arg BUILD_TOOL=maven -t myapp -f Dockerfile.java .
+# docker build --build-arg BUILD_TOOL=gradle -t myapp -f Dockerfile.java .
+#
+# Run (hardened):
+# docker run --rm \
+# --read-only \
+# --cap-drop=ALL \
+# --security-opt=no-new-privileges \
+# myapp
+ARG BUILD_TOOL=maven
+ARG JAVA_VERSION=21
+ARG DISTROLESS_TAG=debian12
+
+# -- Maven: cache pom.xml before source
+FROM eclipse-temurin:${JAVA_VERSION}-jdk-jammy AS maven-deps
+WORKDIR /app
+COPY pom.xml ./
+RUN mvn dependency:go-offline -q
+
+# -- Gradle: cache wrapper + build files before source
+FROM eclipse-temurin:${JAVA_VERSION}-jdk-jammy AS gradle-deps
+WORKDIR /app
+COPY gradlew build.gradle* settings.gradle* ./
+COPY gradle/ ./gradle/
+RUN chmod +x gradlew && ./gradlew dependencies --no-daemon -q
+
+# -- Build: compile and package (inherits deps layer from chosen tool)
+# hadolint ignore=DL3006
+FROM ${BUILD_TOOL}-deps AS builder
+ARG BUILD_TOOL
+WORKDIR /app
+COPY src ./src
+SHELL ["/bin/bash", "-o", "pipefail", "-c"]
+RUN if [ "${BUILD_TOOL}" = "maven" ]; then \
+ mvn package -DskipTests -q && cp target/*.jar /app/app.jar; \
+ else \
+ ./gradlew build --no-daemon -x test -q && cp build/libs/*.jar /app/app.jar; \
+ fi
+
+# -- Runtime
+FROM gcr.io/distroless/java${JAVA_VERSION}-${DISTROLESS_TAG}:nonroot
+WORKDIR /app
+COPY --from=builder --chown=nonroot:nonroot /app/app.jar /app/app.jar
+
+USER nonroot
+
+ENTRYPOINT ["java", "-jar", "/app/app.jar"]
diff --git a/dockerfiles/java/Dockerfile.java.chainguard b/dockerfiles/java/Dockerfile.java.chainguard
new file mode 100644
index 0000000..6327c74
--- /dev/null
+++ b/dockerfiles/java/Dockerfile.java.chainguard
@@ -0,0 +1,36 @@
+# Java application image (Chainguard maven builder + Chainguard jre runtime).
+#
+# Chainguard sibling of Dockerfile.java. Builds with cgr.dev/chainguard/maven
+# and ships in cgr.dev/chainguard/jre — daily-rebuilt, Sigstore-signed, with
+# SLSA provenance and SBOMs attached.
+#
+# Free vs. paid Chainguard tags:
+# The free Developer Edition only publishes :latest. Versioned tags
+# require a paid Chainguard subscription. For free-tier reproducibility,
+# pin by digest instead of relying on :latest:
+#
+# docker pull cgr.dev/chainguard/maven:latest
+# docker inspect --format='{{index .RepoDigests 0}}' cgr.dev/chainguard/maven:latest
+# docker pull cgr.dev/chainguard/jre:latest
+# docker inspect --format='{{index .RepoDigests 0}}' cgr.dev/chainguard/jre:latest
+# # bake the digests into the FROM lines.
+#
+# Refresh digests deliberately (e.g. weekly) and re-scan.
+#
+# Build (free tier -- pin by digest in production):
+# docker build -t myapp -f Dockerfile.java.chainguard .
+ARG BASE_TAG=latest
+
+FROM cgr.dev/chainguard/maven:${BASE_TAG} AS builder
+WORKDIR /app
+COPY pom.xml ./
+RUN mvn dependency:go-offline -q
+COPY src ./src
+RUN mvn package -DskipTests -q && cp target/*.jar /app/app.jar
+
+FROM cgr.dev/chainguard/jre:${BASE_TAG}
+COPY --from=builder /app/app.jar /app/app.jar
+
+USER java
+
+ENTRYPOINT ["java", "-jar", "/app/app.jar"]
diff --git a/dockerfiles/java/Dockerfile.java.native b/dockerfiles/java/Dockerfile.java.native
new file mode 100644
index 0000000..dc8bdb2
--- /dev/null
+++ b/dockerfiles/java/Dockerfile.java.native
@@ -0,0 +1,49 @@
+# Java GraalVM Native Image (native-image-community + Distroless cc, multi-stage).
+#
+# Compiles a Java application to a native binary via GraalVM Native Image.
+# The resulting binary starts in milliseconds (no JVM warm-up) and uses a
+# fraction of the heap compared to the JVM variant.
+#
+# Trade-offs vs. the JVM variant (Dockerfile.java):
+# + Cold start: ~10ms vs. 100-500ms for a typical Spring Boot app.
+# + Memory: native binary uses 2-5x less heap at steady state.
+# - Build time: native-image compilation takes minutes (vs. seconds for javac).
+# - Reflection: dynamic reflection, JNI, and serialization require AOT
+# configuration (reflect-config.json). Spring Boot 3 Native handles this
+# automatically via the GraalVM reachability metadata.
+# - Build platform: the native binary targets the build host OS/arch; cross-
+# compilation requires separate builds per platform.
+#
+# Runtime: gcr.io/distroless/cc-debian12:nonroot — includes glibc and
+# libstdc++ for a dynamically-linked native binary. For a fully-static binary
+# (requires musl toolchain; see README.md), switch to distroless/static.
+#
+# APP_NAME must match the artifact ID / binary name produced by native-image.
+#
+# Build:
+# docker build --build-arg APP_NAME=myapp -t myapp -f Dockerfile.java.native .
+#
+# Run:
+# docker run --rm \
+# --read-only \
+# --cap-drop=ALL \
+# --security-opt=no-new-privileges \
+# myapp
+ARG GRAALVM_TAG=21-ol9
+ARG DISTROLESS_TAG=debian12
+
+FROM ghcr.io/graalvm/native-image-community:${GRAALVM_TAG} AS builder
+ARG APP_NAME=app
+WORKDIR /app
+COPY pom.xml ./
+RUN mvn dependency:go-offline -q
+COPY src ./src
+RUN mvn -Pnative native:compile -DskipTests -q && \
+ cp "target/${APP_NAME}" /native-binary
+
+FROM gcr.io/distroless/cc-${DISTROLESS_TAG}:nonroot
+COPY --from=builder --chown=nonroot:nonroot /native-binary /app/server
+
+USER nonroot
+
+ENTRYPOINT ["/app/server"]
diff --git a/dockerfiles/java/Dockerfile.lambda b/dockerfiles/java/Dockerfile.lambda
new file mode 100644
index 0000000..58ab248
--- /dev/null
+++ b/dockerfiles/java/Dockerfile.lambda
@@ -0,0 +1,40 @@
+# Java AWS Lambda image (Eclipse Temurin builder + Lambda java runtime).
+#
+# Uses Eclipse Temurin to build a fat JAR, then copies it into the AWS Lambda
+# Java base image which ships the Lambda Runtime Interface Client (RIC) and
+# the Lambda Runtime Interface Emulator (RIE) for local testing.
+#
+# Handler class:
+# Set CMD to "com.example.MyHandler::handleRequest" (fully qualified class
+# name :: method name). The Lambda Java runtime invokes this method for
+# each event.
+#
+# SnapStart:
+# Enable AWS Lambda SnapStart (Java 21 Managed Runtime) to take a snapshot
+# after initialization and restore it on cold starts — cuts cold start from
+# ~400ms to ~10ms for typical Spring Boot apps. Requires:
+# - Lambda function runtime: Java 21 (Managed Runtime, not container image)
+# - OR: configure CRaC (Coordinated Restore at Checkpoint) in your app for
+# container-based Lambda SnapStart.
+# See: https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html
+#
+# Build:
+# docker build --platform=linux/amd64 -t myapp-lambda -f Dockerfile.lambda .
+#
+# Local invocation (Lambda Runtime Interface Emulator):
+# docker run --rm -p 9000:8080 myapp-lambda
+# curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" \
+# -d '{"key":"value"}'
+ARG JAVA_VERSION=21
+
+FROM eclipse-temurin:${JAVA_VERSION}-jdk-jammy AS builder
+WORKDIR /app
+COPY pom.xml ./
+RUN mvn dependency:go-offline -q
+COPY src ./src
+RUN mvn package -DskipTests -q
+
+FROM public.ecr.aws/lambda/java:${JAVA_VERSION}
+COPY --from=builder /app/target/*.jar ${LAMBDA_TASK_ROOT}/
+
+CMD ["com.example.Handler::handleRequest"]
diff --git a/dockerfiles/java/README.md b/dockerfiles/java/README.md
new file mode 100644
index 0000000..776cce5
--- /dev/null
+++ b/dockerfiles/java/README.md
@@ -0,0 +1,291 @@
+# Secure Java Docker Image Templates
+
+Reference Dockerfiles for building **secure Java Docker images** in production — including a **Distroless JRE** variant with Maven/Gradle dep caching, a **GraalVM Native Image** variant, a **Chainguard** variant for signed/attested images, an **AWS Lambda** variant, and a **devcontainer** for VS Code.
+
+**Default runtime is [`gcr.io/distroless/java21-debian12:nonroot`](https://github.com/GoogleContainerTools/distroless/blob/main/java/README.md).** This image ships the JRE but no shell, no package manager. Chainguard variants are provided as siblings for users who prefer daily-rebuilt, Sigstore-signed images.
+
+| File | Builder | Runtime base | Use when |
+| ---------------------------- | ----------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------- |
+| `Dockerfile.java` | `eclipse-temurin:21-jdk-jammy` | `gcr.io/distroless/java21-debian12:nonroot` | Default. Maven or Gradle fat JAR on the JVM. |
+| `Dockerfile.java.native` | `ghcr.io/graalvm/native-image-community:21-ol9` | `gcr.io/distroless/cc-debian12:nonroot` | GraalVM Native Image for fast cold starts and lower memory. |
+| `Dockerfile.java.chainguard` | `cgr.dev/chainguard/maven` | `cgr.dev/chainguard/jre` | Chainguard daily CVE patches, Sigstore signatures, SLSA provenance. |
+| `Dockerfile.lambda` | `eclipse-temurin:21-jdk-jammy` | `public.ecr.aws/lambda/java:21` | AWS Lambda container image with Lambda RIC pre-installed. |
+| `Dockerfile.devcontainer` | — | `mcr.microsoft.com/devcontainers/java:1-21-*` | VS Code Remote-Containers / Dev Containers development environment. |
+
+## Why these images are efficient
+
+### Maven dep-cache layer
+
+Copy `pom.xml` before source so the dependency-download layer is only invalidated when dependencies change:
+
+```dockerfile
+COPY pom.xml ./
+RUN mvn dependency:go-offline -q # cached layer
+COPY src ./src
+RUN mvn package -DskipTests -q
+```
+
+Changing only `src/` reuses the `mvn dependency:go-offline` layer. Re-builds for code-only changes take seconds instead of minutes.
+
+### Gradle dep-cache layer
+
+Copy the Gradle wrapper and build files before source:
+
+```dockerfile
+COPY gradlew build.gradle* settings.gradle* ./
+COPY gradle/ ./gradle/
+RUN chmod +x gradlew && ./gradlew dependencies --no-daemon -q # cached layer
+COPY src ./src
+RUN ./gradlew build --no-daemon -x test -q
+```
+
+### Maven vs Gradle via ARG BUILD_TOOL
+
+`Dockerfile.java` supports both build tools via `--build-arg BUILD_TOOL=maven` (default) or `--build-arg BUILD_TOOL=gradle`. BuildKit builds only the stage that matches — the unused tool's dep-cache stage is never executed.
+
+```bash
+# Maven project (default)
+docker build --build-arg BUILD_TOOL=maven -t myapp -f Dockerfile.java .
+
+# Gradle project
+docker build --build-arg BUILD_TOOL=gradle -t myapp -f Dockerfile.java .
+```
+
+### Multi-stage build
+
+The builder stage (Temurin JDK + Maven/Gradle) stays out of the final image. Only the compiled JAR is copied into the distroless JRE runtime. Result: the final image contains the JRE and your JAR — no build toolchain.
+
+### Spring Boot layered JARs
+
+Spring Boot 2.3+ supports layered JARs that split the fat JAR into distinct layers. Use this to get much better Docker cache utilisation for framework applications:
+
+```dockerfile
+FROM eclipse-temurin:21-jdk-jammy AS builder
+# ... build fat JAR ...
+RUN java -Djarmode=layertools -jar target/app.jar extract
+
+FROM gcr.io/distroless/java21-debian12:nonroot
+COPY --from=builder /app/dependencies /
+COPY --from=builder /app/spring-boot-loader /
+COPY --from=builder /app/snapshot-dependencies /
+COPY --from=builder /app/application /
+ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]
+```
+
+The `dependencies` layer (all stable library JARs) is rarely invalidated. Only the `application` layer (your compiled classes) changes on code edits.
+
+## JVM vs GraalVM Native Image
+
+| Metric | JVM (`Dockerfile.java`) | Native Image (`Dockerfile.java.native`) |
+| ------------------- | ----------------------- | --------------------------------------- |
+| Cold start | 100–500 ms | 10–50 ms |
+| Steady-state RSS | 200–500 MB | 50–150 MB |
+| Build time | Seconds | Minutes |
+| Reflection | Dynamic (no config) | Requires AOT config or Spring Native |
+| JNI / Proxies | Transparent | Requires reachability metadata |
+| Profile-guided opts | Yes (JIT) | Requires PGO instrumentation build |
+
+### GraalVM Native Image (`Dockerfile.java.native`)
+
+The `Dockerfile.java.native` uses `ghcr.io/graalvm/native-image-community:21-ol9` to compile to a native binary via `mvn -Pnative native:compile`. The runtime is `gcr.io/distroless/cc-debian12:nonroot` (glibc-linked).
+
+For **Spring Boot 3** applications, add the Spring AOT plugin:
+
+```xml
+
+ org.graalvm.buildtools
+ native-maven-plugin
+
+```
+
+The plugin generates the AOT configuration (reflection, serialization, proxies) automatically at build time.
+
+#### Fully-static native binary (musl)
+
+To produce a fully-static binary and ship in `distroless/static-debian12:nonroot`:
+
+1. Install the musl toolchain in the builder stage:
+
+```dockerfile
+RUN dnf install -y gcc musl-gcc && \
+ curl -fsSL https://musl.libc.org/releases/musl-1.2.5.tar.gz | tar xz && \
+ cd musl-1.2.5 && ./configure --prefix=/usr/local/musl && make install
+```
+
+1. Compile with `--static --libc=musl`:
+
+```dockerfile
+RUN native-image --static --libc=musl -jar target/app.jar app
+```
+
+1. Switch the runtime to `gcr.io/distroless/static-debian12:nonroot`.
+
+## Why these images are secure
+
+### No shell in the JVM runtime
+
+`distroless/java21-debian12:nonroot` ships the JRE, CA certificates, and tzdata. There is no `/bin/sh`, no package manager, no curl. A compromised JVM process cannot drop into a shell.
+
+### Non-root by default
+
+All production final stages run as a non-root user (`nonroot` UID 65532 for distroless, `java` for Chainguard JRE). The JAR is `--chown=nonroot:nonroot` so the process cannot overwrite it.
+
+## Build and run
+
+```bash
+# Distroless JVM (Maven, default)
+docker build --build-arg BUILD_TOOL=maven -t myapp -f Dockerfile.java .
+
+# Distroless JVM (Gradle)
+docker build --build-arg BUILD_TOOL=gradle -t myapp -f Dockerfile.java .
+
+# GraalVM Native Image
+docker build --build-arg APP_NAME=myapp -t myapp-native -f Dockerfile.java.native .
+
+# Chainguard (free tier -- pin by digest in production)
+docker build -t myapp -f Dockerfile.java.chainguard .
+
+# Lambda (match your function's architecture)
+docker build --platform=linux/amd64 -t myapp-lambda -f Dockerfile.lambda .
+
+# Multi-arch JVM
+docker buildx build --platform=linux/amd64,linux/arm64 \
+ --build-arg BUILD_TOOL=maven -t myapp -f Dockerfile.java .
+
+# Run (hardened)
+docker run --rm \
+ --read-only \
+ --cap-drop=ALL \
+ --security-opt=no-new-privileges \
+ myapp
+```
+
+## Expected build-context layout
+
+```text
+.
+├── Dockerfile.java # or .native / .chainguard / .lambda
+├── .dockerignore # provided in this directory
+├── pom.xml # Maven: required for dep-cache layer
+├── src/
+│ └── main/java/
+│ └── com/example/
+│ └── Main.java
+```
+
+For Gradle projects, replace `pom.xml` with `build.gradle` / `settings.gradle` / `gradlew` / `gradle/`.
+
+## Lambda variant
+
+The Lambda Java base image (`public.ecr.aws/lambda/java:21`) ships the AWS Lambda Runtime Interface Client. The fat JAR is copied to `${LAMBDA_TASK_ROOT}`. Set `CMD` to the fully-qualified handler class and method:
+
+```dockerfile
+CMD ["com.example.Handler::handleRequest"]
+```
+
+### Local invocation
+
+```bash
+docker run --rm -p 9000:8080 myapp-lambda
+curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" \
+ -d '{"key":"value"}'
+```
+
+### AWS Lambda SnapStart
+
+SnapStart (available on the Java 21 Managed Runtime) snapshots the initialized Lambda execution environment and restores it on cold starts — reducing cold start latency from ~400 ms to ~10 ms. To use SnapStart with a container image, configure [CRaC (Coordinated Restore at Checkpoint)](https://docs.aws.amazon.com/lambda/latest/dg/snapstart-supported-states.html) in your application and implement the `CracResource` interface to clean up and restore resources across checkpoints.
+
+## Chainguard digest-pinning
+
+The Chainguard free tier publishes only `:latest`. For reproducible builds, pin by digest:
+
+```bash
+docker pull cgr.dev/chainguard/maven:latest
+docker inspect --format='{{index .RepoDigests 0}}' cgr.dev/chainguard/maven:latest
+
+docker pull cgr.dev/chainguard/jre:latest
+docker inspect --format='{{index .RepoDigests 0}}' cgr.dev/chainguard/jre:latest
+```
+
+Then replace the `FROM` lines with the digest form:
+
+```dockerfile
+FROM cgr.dev/chainguard/maven@sha256: AS builder
+# ...
+FROM cgr.dev/chainguard/jre@sha256:
+```
+
+## Devcontainer variant
+
+`Dockerfile.devcontainer` is based on `mcr.microsoft.com/devcontainers/java:1-21-bookworm` and adds:
+
+- Gradle (pinned) for projects not using the Gradle wrapper
+- `jq` and `curl` for common dev tasks
+
+The base image ships Maven, git, and common dev utilities.
+
+The companion `.devcontainer/devcontainer.json` wires up:
+
+- `redhat.java` Language Support for Java
+- `vscjava.vscode-maven` and `vscjava.vscode-gradle` for build tool integration
+- `vscjava.vscode-java-test` Test Runner UI
+- `vscjava.vscode-spring-initializr` and `vmware.vscode-spring-boot` for Spring apps
+- `postCreateCommand: mvn dependency:go-offline` to warm the dep cache on start
+
+### Reopen in Container
+
+1. Open the `dockerfiles/java/` folder in VS Code.
+2. When prompted "Reopen in Container", click yes — or use `Ctrl+Shift+P` → **Dev Containers: Reopen in Container**.
+3. VS Code builds `Dockerfile.devcontainer`, mounts the workspace, and installs extensions.
+
+## JLink custom JRE
+
+For a middle ground between the full JDK and distroless, `jlink` can produce a minimal custom JRE that includes only the modules your application uses:
+
+```dockerfile
+FROM eclipse-temurin:21-jdk-jammy AS jlink
+RUN jlink \
+ --add-modules java.base,java.logging,java.sql,java.net.http \
+ --strip-debug \
+ --no-man-pages \
+ --no-header-files \
+ --compress=zip-6 \
+ --output /jre-custom
+
+FROM debian:bookworm-slim
+COPY --from=jlink /jre-custom /opt/java
+ENV PATH="/opt/java/bin:${PATH}"
+# ...
+```
+
+This is not provided as a separate Dockerfile but is a useful technique documented here. The modules list must be tuned per application via `jdeps --print-module-deps`.
+
+## Multi-arch builds
+
+GraalVM Native Image does **not** support cross-compilation — you must build natively on each target platform. Use separate `docker buildx build --platform=linux/amd64` and `--platform=linux/arm64` commands (or separate CI jobs) for the native variant.
+
+The JVM variant (`Dockerfile.java`) supports multi-arch:
+
+```bash
+docker buildx create --use
+
+docker buildx build \
+ --platform=linux/amd64,linux/arm64 \
+ --push \
+ --build-arg BUILD_TOOL=maven \
+ -t myregistry/myapp:latest \
+ -f Dockerfile.java .
+```
+
+## Hardening checklist
+
+- [ ] Pin `JAVA_VERSION` to `21` (or your target LTS) — never use `latest`.
+- [ ] Pin the base image by digest in production (`gcr.io/distroless/java21-debian12@sha256:…`).
+- [ ] Commit `pom.xml` / `build.gradle` with exact dependency versions (no SNAPSHOT ranges in production).
+- [ ] Pass `-DskipTests` only in the Docker build; run tests in CI before building the image.
+- [ ] For Spring Boot: enable layered JARs to improve cache utilisation (see above).
+- [ ] For GraalVM: run `-DskipTests=false` in native profile in CI to catch AOT reflection issues.
+- [ ] Run with `--read-only`, `--cap-drop=ALL`, `--security-opt=no-new-privileges`.
+- [ ] Set resource limits (`--memory`, `--cpus`) — the JVM will size its heap to the container limit.
+- [ ] Scan the built image (`grype`, `trivy`) before publishing.
+- [ ] Sign the image and SLSA provenance with Cosign — see [`docs/supply-chain.md`](../../docs/supply-chain.md).