# 前提:CI 的 validate 阶段已经跑过 ./gradlew :bootstrap:bootJar, # 产物通过 GitLab artifacts 传递到 package 阶段,这里直接用,不重新编译。 # 见 09-build-deploy.md「Dockerfile:复用 CI 产物 + 分层解包」。 FROM eclipse-temurin:21-jre AS layers WORKDIR /layers COPY bootstrap/build/libs/*.jar app.jar RUN java -Djarmode=tools -jar app.jar extract --layers --launcher --destination . FROM eclipse-temurin:21-jre WORKDIR /app # 非 root 运行:容器内一旦被攻破,攻击者拿到的也只是一个无特权用户。 # USER 写数字而不是 appuser,是为了让 K8s 的 runAsNonRoot: true 能在启动前静态校验通过 # (K8s 无法解析镜像里的用户名,只认数字 UID)。 RUN useradd --system --uid 10001 --create-home appuser USER 10001 # 按变更频率从低到高逐层 COPY,前三层几乎不变,可以吃满 Docker layer 缓存, # 每次发版真正推送到 ACR 的通常只有最后一层(几百 KB 的业务代码) COPY --from=layers --chown=10001:10001 /layers/dependencies/ ./ COPY --from=layers --chown=10001:10001 /layers/spring-boot-loader/ ./ COPY --from=layers --chown=10001:10001 /layers/snapshot-dependencies/ ./ COPY --from=layers --chown=10001:10001 /layers/application/ ./ # MaxRAMPercentage:JVM 在容器里默认只用可用内存的 25% 做堆,配 2Gi 的 Pod 堆只有 512Mi。 # ExitOnOutOfMemoryError:OOM 直接结束进程交给 K8s 重启,而不是留一个探针还返回健康的半死 Pod。 ENTRYPOINT ["java", \ "-XX:MaxRAMPercentage=75.0", \ "-XX:+ExitOnOutOfMemoryError", \ "org.springframework.boot.loader.launch.JarLauncher"]