init: 클린 아키텍처 백엔드

This commit is contained in:
DongHyeonka
2026-07-24 14:29:36 +09:00
parent 9eed16d097
commit 821fe00c32
971 changed files with 74769 additions and 1 deletions
+139
View File
@@ -0,0 +1,139 @@
# 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
# 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 --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 build the JAR
COPY . .
RUN ./gradlew :app-bootstrap:bootJar --no-daemon -x test \
-PreleaseVersion="${RELEASE_VERSION}" -PgitRevision="${GIT_SHA}"
# Locate the produced JAR (avoids hardcoding the version string)
RUN cp $(ls app-bootstrap/build/libs/*.jar | grep -v plain | head -1) /build/app.jar
# ---- 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
WORKDIR /app
COPY --from=builder --chown=app:app /build/app.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"]