Vai al contenuto

Step 10 — Docker multi-stage + compose

Obiettivo: immagine Docker piccola, layered, reproducible.

Dockerfile

Dockerfile
# Stage 1 — Build
FROM eclipse-temurin:21-jdk AS build
WORKDIR /workspace
COPY .mvn/ .mvn/
COPY mvnw pom.xml ./
RUN chmod +x mvnw && ./mvnw -B -ntp dependency:go-offline
COPY src src
RUN ./mvnw -B -ntp -DskipTests package && \
    cp target/*.jar app.jar && \
    java -Djarmode=tools -jar app.jar extract --layers --destination extracted

# Stage 2 — Runtime
FROM eclipse-temurin:21-jre AS runtime
RUN groupadd --system spring && useradd --system --gid spring spring
WORKDIR /app
COPY --from=build /workspace/extracted/dependencies/ ./
COPY --from=build /workspace/extracted/spring-boot-loader/ ./
COPY --from=build /workspace/extracted/snapshot-dependencies/ ./
COPY --from=build /workspace/extracted/application/ ./
USER spring:spring
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "org.springframework.boot.loader.launch.JarLauncher"]

Perché multi-stage

  • Lo stage 1 ha tutto il JDK + Maven (~500 MB). Si butta via.
  • Lo stage 2 ha solo JRE + bytecode (~250 MB).

Perché layered JAR

Spring Boot 2.3+ può estrarre il JAR in 4 strati ordinati per "cambiabilità":

  1. dependencies — cambia di rado → cache Docker valida a lungo
  2. spring-boot-loader — quasi mai
  3. snapshot-dependencies — qualche volta
  4. application — ogni build

Risultato: rebuild incrementali velocissimi.

Sicurezza

  • Utente non-root (spring:spring).
  • MaxRAMPercentage=75 per rispettare i limiti del container.
  • Healthcheck interno.

docker-compose

compose.yaml (estratto)
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: sbfs
      POSTGRES_USER: sbfs
      POSTGRES_PASSWORD: sbfs
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sbfs"]

  app:
    build: .
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: prod
      SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/sbfs

depends_on: condition: service_healthy evita la race condition tra app e DB.

Esercizi

  1. Aggiungi un servizio Grafana al compose con dashboard di base.
  2. Crea un'immagine distroless sostituendo eclipse-temurin:21-jre con gcr.io/distroless/java21-debian12.
  3. Confronta dimensioni e tempi di avvio dell'immagine standard vs. distroless.

Riferimenti nel codice