# syntax=docker/dockerfile:1.7-labs@sha256:b99fecfe00268a8b556fad7d9c37ee25d716ae08a5d7320e6d51c4dd83246894 # ============================================================================= # sample-portfolio standalone demo image — twin of src/Dockerfile. # # Builds and runs the REFERENCE app (SamplePortfolioApplication), not the # production composition root (CaSkeletonApplication) that src/Dockerfile builds. # The sample is the demo-friendly entrypoint: its application.yml self-provides # defaults for every env placeholder, so the only external dependency it needs # to boot is a reachable PostgreSQL (datasource + Flyway sample migrations). # # Build (no build-args required — this is a disposable demo, not a release artifact): # docker build -f src/Dockerfile.sample src/ -t ca-sample:local # # Run (point APP_DATASOURCE_URL at a reachable Postgres; localhost default shown): # docker run --rm -p 8080:8080 -p 9001:9001 \ # -e APP_DATASOURCE_URL=jdbc:postgresql://host.docker.internal:5432/ca_skeleton \ # ca-sample:local # # The multi-stage build, --parents descriptor glob, STRICT lock verification, # non-root user, read-only-root-fs writable mounts, and JVM container ergonomics # are all identical to src/Dockerfile — only the bootJar target differs. Keep the # two files' builder stages in sync. # ============================================================================= # Demo defaults so the image builds with zero build-args. Two format constraints from the root # build.gradle configuration guard (feature-build-release-supply-chain-contract D1/D9): # - RELEASE_VERSION must be bare MAJOR.MINOR.PATCH — no pre-release/build suffix (build.gradle L21). # The "-sample" marker therefore lives only on BUILD_VERSION, which is a label, not a gradle prop. # - GIT_SHA must be 7-40 hex chars (build.gradle L35); 0000000 is the placeholder. ARG RELEASE_VERSION=0.0.0 ARG BUILD_VERSION=0.0.0-sample ARG GIT_SHA=0000000 ARG SOURCE_URL=https://example.invalid/ca-tmpl-sample # ---- 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 a per-module COPY list: new modules are picked up # automatically and this stage never drifts out of sync with settings.gradle. # (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 demo build either). 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 sample JAR at Gradle's declared Docker output path. COPY . . RUN ./gradlew :sample-portfolio:stageDockerJar --no-daemon -x test \ -PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}" # ---- Stage 2: runtime image ------------------------------------------------- # JRE-only slim image (no full JDK in the demo image either). FROM eclipse-temurin:21-jre-jammy@sha256:199aebeb3adcde4910695cdebfe782ada38dadb6cc8013159b58d3724451befd AS runtime ARG BUILD_VERSION ARG GIT_SHA ARG SOURCE_URL # OCI image labels. Unlike the release image (src/Dockerfile), the demo image does NOT hard-fail # on missing metadata — the ARG defaults above keep it buildable with no build-args. LABEL org.opencontainers.image.title="caskeleton-sample" \ org.opencontainers.image.description="ca-tmpl sample-portfolio reference/demo application" \ org.opencontainers.image.source="${SOURCE_URL}" \ org.opencontainers.image.revision="${GIT_SHA}" \ org.opencontainers.image.version="${BUILD_VERSION}" # ---- Locale / timezone ------------------------------------------------------ ENV TZ=UTC \ LANG=C.UTF-8 \ LC_ALL=C.UTF-8 # ---- Writable HOME under read-only root fs ---------------------------------- ENV HOME=/tmp # ---- JVM ergonomics --------------------------------------------------------- # Identical to src/Dockerfile: container-aware heap, fail-fast on OOM, heap dump to a # writable mount, and Tomcat temp redirected to /tmp for a read-only root filesystem. ENV JAVA_TOOL_OPTIONS="\ -XX:MaxRAMPercentage=75 \ -XX:+UseContainerSupport \ -XX:+ExitOnOutOfMemoryError \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/var/tmp/heap \ -Dserver.tomcat.basedir=/tmp" # ---- Filesystem layout (read-only root filesystem) -------------------------- # At runtime /var/tmp/heap and /tmp MUST be writable mounts (tmpfs/emptyDir). RUN mkdir -p /var/tmp/heap && chmod 1777 /var/tmp/heap # ---- Non-root user ---------------------------------------------------------- RUN groupadd --system --gid 1000 app \ && useradd --system --uid 1000 --gid app --no-create-home --shell /usr/sbin/nologin app WORKDIR /app COPY --from=builder --chown=app:app /build/src/sample-portfolio/build/docker/application.jar app.jar USER app # ---- Ports ------------------------------------------------------------------ # 8080 — application HTTP port # 9001 — management / actuator port EXPOSE 8080 9001 # ---- Health check ----------------------------------------------------------- # Actuator readiness probe on the management port (9001). Ignored by Kubernetes, # which uses its own probes — kept for docker/compose parity with src/Dockerfile. 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 ------------------------------------------------------------- # mainClass (SamplePortfolioApplication) is baked into the bootJar manifest. ENTRYPOINT ["java", "-jar", "/app/app.jar"]