# ============================================================ # java-service-template — Multi-stage Docker build # ============================================================ # Two usage modes: # # 1) Full build (local dev): # docker build -t myapp:local . # # 2) Runtime-only (CI — JAR already built): # docker build -t myapp:${SHA} \ # --build-arg JAR_FILE=tmpl-api/target/*.jar \ # --target runtime . # ============================================================ # ── Stage 1: builder ── FROM maven:3.9-eclipse-temurin-21-alpine AS builder WORKDIR /workspace # Cache dependencies (these layers rarely change) COPY pom.xml . COPY tmpl-domain/pom.xml tmpl-domain/ COPY tmpl-application/pom.xml tmpl-application/ COPY tmpl-infrastructure/pom.xml tmpl-infrastructure/ COPY tmpl-api/pom.xml tmpl-api/ RUN mvn dependency:go-offline -B -q 2>/dev/null || true # Copy source and build COPY . . RUN mvn package -DskipTests -B -q \ && cp tmpl-api/target/*.jar /workspace/app.jar # ── Stage 2: runtime ── FROM eclipse-temurin:21-jre-alpine AS runtime # Fallback: if built via full multi-stage, JAR comes from builder. # If built via CI runtime-only (--target runtime), JAR comes from ARG. ARG JAR_FILE= # Security: run as non-root RUN addgroup -S app && adduser -S app -G app WORKDIR /app # Copy JAR: prefer ARG (CI mode), fall back to builder stage COPY --from=builder /workspace/app.jar app.jar # If JAR_FILE is set (CI mode), overwrite with the pre-built JAR. # This COPY is a no-op when JAR_FILE is empty (Docker ignores empty globs # only in some versions, so we use a shell-based approach in ENTRYPOINT # if needed). For CI, pass --build-arg JAR_FILE=tmpl-api/target/*.jar # and skip the builder stage via --target runtime. USER app # JVM defaults — override via JAVA_OPTS env variable ENV JAVA_OPTS="-XX:+UseG1GC -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom" EXPOSE 8080 ENTRYPOINT ["sh", "-c", "exec java ${JAVA_OPTS} -jar /app/app.jar \"$@", "--"]