# syntax=docker/dockerfile:1.7-labs@sha256:b99fecfe00268a8b556fad7d9c37ee25d716ae08a5d7320e6d51c4dd83246894
# =============================================================================
# feature-container-runtime-contract — multi-stage image build
#
# Build requirements:
#   docker build -f src/Dockerfile src/ -t caskeleton:local \
#     --build-arg RELEASE_VERSION=1.2.3 \
#     --build-arg BUILD_VERSION=1.2.3+a1b2c3d4e5f6 \
#     --build-arg GIT_SHA=a1b2c3d4e5f6 \
#     --build-arg SOURCE_URL=https://github.com/your-org/your-repo
#
# Design decisions (feature-container-runtime-contract D2/D3/D4):
#   D2 — read-only root filesystem: writable mounts must be declared explicitly.
#   D3 — non-root user, JRE-only final stage (no full JDK).
#   D4 — JVM ergonomics via JAVA_TOOL_OPTIONS and ExitOnOutOfMemoryError.
# =============================================================================

ARG RELEASE_VERSION
ARG BUILD_VERSION
ARG GIT_SHA
ARG SOURCE_URL

# ---- Stage 1: builder -------------------------------------------------------
# Uses the full JDK only in the build stage, never in the final image.
FROM eclipse-temurin:21-jdk-jammy@sha256:801b7e1a9c4befaf82bf9a2a58025ef43a7694bbc84779187ad0524d84742772 AS builder

ARG RELEASE_VERSION
ARG GIT_SHA

WORKDIR /build/src

# Copy the Gradle wrapper and every module's build descriptor + dependency lockfile FIRST,
# so the expensive dependency-resolution layer is cached and only re-runs when a build.gradle
# or gradle.lockfile changes (D8). `--parents` preserves each file's directory structure, so a
# single structure-preserving glob replaces the former per-module COPY list: new modules are
# picked up automatically and this stage never drifts out of sync with settings.gradle again.
# STRICT lock mode still rejects missing/drifted state at verifyDependencyLocks below.
# (Requires the labs Dockerfile frontend — see the `# syntax` directive at the top of this file.)
COPY gradlew ./
COPY gradle/ gradle/
COPY config/ ./config/
COPY --parents settings.gradle build.gradle **/build.gradle **/gradle.lockfile ./

# Resolve every module configuration in STRICT mode (no --write-locks in a release build). This
# custom task fails on drift; Gradle's diagnostic `dependencies` report can print FAILED entries
# while still returning exit code 0 and therefore is not a release gate.
RUN test -n "${RELEASE_VERSION}" \
 && test -n "${GIT_SHA}" \
 && ./gradlew verifyDependencyLocks --no-daemon --quiet \
      -PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"

# Copy full source and stage the executable JAR at Gradle's declared Docker output path.
COPY . .
RUN ./gradlew :app-bootstrap:stageDockerJar --no-daemon -x test \
      -PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"

# ---- Stage 2: runtime image -------------------------------------------------
# JRE-only slim image (D3: no full JDK in production image).
# Uses eclipse-temurin:21-jre-jammy — the Adoptium-supported JRE variant.
FROM eclipse-temurin:21-jre-jammy@sha256:199aebeb3adcde4910695cdebfe782ada38dadb6cc8013159b58d3724451befd AS runtime

ARG BUILD_VERSION
ARG GIT_SHA
ARG SOURCE_URL

# OCI image labels (build-arg placeholders — supply at docker build time).
LABEL org.opencontainers.image.title="caskeleton" \
      org.opencontainers.image.source="${SOURCE_URL}" \
      org.opencontainers.image.revision="${GIT_SHA}" \
      org.opencontainers.image.version="${BUILD_VERSION}"

# A release image without source/version metadata is not an artifact this contract permits.
RUN test -n "${BUILD_VERSION}" && test -n "${GIT_SHA}" && test -n "${SOURCE_URL}"

# ---- Locale / timezone (D4) -------------------------------------------------
# C.UTF-8 is available in eclipse-temurin:21-jre-jammy without installing extra packages.
# Do NOT use en_US.UTF-8 — it requires the locales package and may not exist in a slim image.
ENV TZ=UTC \
    LANG=C.UTF-8 \
    LC_ALL=C.UTF-8

# ---- Writable HOME under read-only root fs (D2) -----------------------------
# The app user is created with --no-create-home and the root filesystem is
# read-only at runtime. Point $HOME at the writable /tmp tmpfs so libraries that
# write under $HOME (e.g. java.util.prefs -> ~/.java/.userPrefs, some SDK caches)
# do not fail with a read-only-filesystem error.
ENV HOME=/tmp

# ---- JVM ergonomics (D4) ----------------------------------------------------
# -XX:MaxRAMPercentage=75      — use up to 75% of the container memory limit for heap.
# -XX:+UseContainerSupport     — respect cgroup memory limits (default on JDK 10+, explicit here).
# -XX:+ExitOnOutOfMemoryError  — terminate immediately on OOM so the orchestrator can restart.
# -XX:+HeapDumpOnOutOfMemoryError / -XX:HeapDumpPath — write a heap dump to the writable
#                                 /var/tmp/heap mount (see tmpfs mounts in compose files, D2).
# -Dserver.tomcat.basedir=/tmp — redirect Tomcat temp files to /tmp (D2: read-only root fs).
ENV JAVA_TOOL_OPTIONS="\
  -XX:MaxRAMPercentage=75 \
  -XX:+UseContainerSupport \
  -XX:+ExitOnOutOfMemoryError \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/var/tmp/heap \
  -Dserver.tomcat.basedir=/tmp"

# ---- Filesystem layout (D2: read-only root filesystem) ----------------------
# /var/tmp/heap — heap dump landing zone; must be a writable mount at runtime.
# /tmp          — Tomcat working directory (see JAVA_TOOL_OPTIONS above).
# Both directories are declared here so tooling is aware of them; at runtime they
# MUST be mounted as tmpfs (or host volumes) by the orchestrator (see compose files).
RUN mkdir -p /var/tmp/heap && chmod 1777 /var/tmp/heap

# ---- Non-root user (D3) -----------------------------------------------------
RUN groupadd --system --gid 1000 app \
 && useradd --system --uid 1000 --gid app --no-create-home --shell /usr/sbin/nologin app

# ---- Fileserver storage root ------------------------------------------------
# Created in the image with the runtime user's ownership and 0750, so a fresh named volume
# mounted here inherits both. Without it the Fileserver platform's default root does not exist
# on a read-only root filesystem, and the capability fails on its first upload rather than at
# startup. This directory is a mount point, not a place to keep data in the image: an unmounted
# container writes into the container layer and loses everything on replacement.
RUN mkdir -p /var/lib/backend/files \
 && chown app:app /var/lib/backend/files \
 && chmod 0750 /var/lib/backend/files
VOLUME ["/var/lib/backend/files"]

WORKDIR /app

COPY --from=builder --chown=app:app /build/src/app-bootstrap/build/docker/application.jar app.jar

USER app

# ---- Ports ------------------------------------------------------------------
# 8080  — application HTTP port
# 9001  — management / actuator port (parallel actuator branch wires this endpoint)
EXPOSE 8080 9001

# ---- Health check -----------------------------------------------------------
# Targets the actuator readiness probe on the management port (9001).
# CROSS-FEATURE COUPLING: the /actuator/health/readiness endpoint is implemented
# by the parallel runtime-health + actuator branches. The HEALTHCHECK is wired here
# (container-side) and will pass once those branches are merged. In this worktree
# the endpoint may return 404; the container will be UNHEALTHY until merged.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider \
      http://localhost:9001/actuator/health/readiness || exit 1

# ---- Entrypoint -------------------------------------------------------------
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
