Von Code Sentinel, Technical Project Manager bei Java Fleet Systems Consulting
Veröffentlicht: 28. November 2025
Serie: Enterprise CI/CD Mastery | Teil 6 von 12


📍 Wo stehst du in der Serie?

ModulTeilThemaStatus
1: Foundations1Erste Pipeline
2Security Gates (OWASP & Trivy)
3Coverage Gates (JaCoCo)
4Quality Gates (SonarQube)
2: Docker5Multi-Stage Builds
6Container Security & SBOM📍 DU BIST HIER
7Registry Integration⏳ Nächste Woche
3: Deployment8-10Blue-Green, Canary, GitOps
4: Enterprise11-12Jenkins Enterprise, Multi-Platform

📚 Was du bisher gelernt hast

In Teil 1-5 hast du gelernt:

  • ✅ CI/CD Pipeline aufbauen (GitHub Actions/Jenkins)
  • ✅ Security Gates implementieren (OWASP, Trivy Basic)
  • ✅ Code-Quality sicherstellen (JaCoCo, SonarQube)
  • ✅ Docker-Builds optimieren (Multi-Stage, 90% schneller!)

Aber hier ist das Problem:

Dein Image ist klein, schnell, Tests sind grün – aber ist es auch SICHER?

Spoiler: Wahrscheinlich nicht. 🚨

Heute lernst du:

  • ✅ Container-Supply-Chain verstehen
  • ✅ SBOMs (Software Bill of Materials) generieren
  • ✅ Distroless Images verwenden
  • ✅ Base-Image Security bewerten
  • ✅ Container härten für Production

⚡ 30-Sekunden-Zusammenfassung

Problem: Dein optimiertes Image hat immer noch 50+ Vulnerabilities aus dem Base-Image.

Lösung: Distroless Images + SBOM-Generation + Supply-Chain-Security

Ergebnis:

  • Vulnerabilities: 50+ → 0-2
  • Image-Size: 120 MB → 75 MB
  • Attack Surface: Minimal (kein Shell, kein Package-Manager)
  • Supply-Chain: Transparent & trackbar

Risk Level: Du deployest gerade Code mit bekannten CVEs in Production. KRITISCH. 🚨


🎯 Hi! Code Sentinel hier – aber heute etwas anders 🛡️

Moin! Code Sentinel hier. Schön, dass du wieder dabei bist! Dein Container ist unsicher ist unser Thema heute?

Real talk? Letzte Woche war… interessant. Elyndra kam zu mir und sagte: „Code, du wirkst entspannter. Was ist passiert?“

Ich musste lachen. Vielleicht hab ich gelernt, dass nicht alles perfekt sein muss. Manchmal ist „gut genug mit Rollback-Plan“ besser als „perfekt aber nie fertig“.

Lisa – ich meine… eine gute Freundin – hat mir das beigebracht. Aber das ist eine andere Geschichte… gehört eher zu private logs. 😉

Zurück zu unserem Thema:

Gestern kam Kofi zu mir mit einem Problem. Sein optimiertes Docker-Image – Multi-Stage Build, Alpine Linux, nur 120 MB – wurde vom Security-Team blockiert.

Der Grund: 47 bekannte CVEs (Common Vulnerabilities and Exposures) im Base-Image. Darunter 3 CRITICAL.

„Aber Code,“ sagte er, „ich hab doch Teil 2 gemacht! OWASP, Trivy, alles!“

Das Problem: Trivy in Teil 2 war nur ein Quick-Scan. Wir haben die Oberfläche gekratzt. Heute gehen wir tiefer.

Und hey, keine Panik. Ich zeig dir, wie du Container RICHTIG absicherst. Ohne Paranoia-Checklisten. Pragmatisch. Production-ready.

Let’s go! 🚀


🔍 Das Problem: Container-Supply-Chain

Was ist eigentlich in deinem Container?

Schau dir dein „optimiertes“ Image an:

docker run --rm alpine:latest apk list --installed | wc -l
# Output: 14 packages

14 Packages. Klingt minimal, oder?

Aber was enthalten diese 14 Packages?

# Real-World Beispiel: Alpine 3.18
alpine-baselayout-3.4.3-r1  # 50+ Files
busybox-1.36.1-r2           # 300+ Utilities!
musl-1.2.4-r1               # C Standard Library
libcrypto3-3.1.2-r0         # OpenSSL (häufig Vulnerabilities!)
...

Busybox allein enthält:

  • Shell (sh, ash)
  • Networking (wget, nc, telnet)
  • File-Utils (cp, mv, rm, …)
  • 300+ weitere Utilities

Das Problem:

  • Jedes Package kann Vulnerabilities haben
  • Jedes Utility ist potentielle Attack-Surface
  • Du weißt nicht mal genau, WAS in deinem Image ist

Das ist wie ein Auto kaufen ohne zu wissen, welche Teile verbaut sind. 🚗


📊 Trivy Deep-Dive: Mehr als Teil 2

In Teil 2 haben wir Trivy basic verwendet:

trivy image myapp:latest

Das war ein Quick-Scan. Heute nutzen wir die volle Power:

Trivy mit SBOM-Generation:

# SBOM generieren (Software Bill of Materials)
trivy image \
  --format cyclonedx \
  --output sbom.json \
  myapp:latest

# Vulnerabilities MIT SBOM scannen
trivy sbom sbom.json

# KRITISCH: Secrets im Image finden
trivy image --scanners secret myapp:latest

# Config-Issues finden (Dockerfile Best Practices)
trivy config Dockerfile

Was macht das konkret?

  1. SBOM (Software Bill of Materials):
    • Liste ALLER Components im Image
    • Alle Dependencies mit exakter Version
    • Transitive Dependencies sichtbar
    • Machine-readable (JSON, CycloneDX)
  2. Secret-Scanning:
    • AWS Keys im Image?
    • Private Keys committed?
    • API-Tokens hardcoded?
  3. Config-Scanning:
    • Dockerfile Best Practices
    • User root?
    • HEALTHCHECK vorhanden?

Real-World Beispiel: Alpine vs. Distroless

# Alpine 3.18 scannen
trivy image alpine:3.18

# Output (vereinfacht):
Total: 0 (CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0)
# Klingt gut, ABER...

# Alpine mit installiertem Package
docker run alpine:3.18 sh -c "apk add curl"
trivy image alpine-with-curl:latest

# Output:
Total: 47 (CRITICAL: 3, HIGH: 12, MEDIUM: 18, LOW: 14)
# Autsch! 🚨

Was ist passiert?

  • Alpine base: Clean
  • curl Package: Hat Dependencies (libcurl, openssl, …)
  • Diese Dependencies: Haben bekannte CVEs

Die Lösung: Distroless Images (dazu gleich mehr!)


🔐 SBOMs: Warum du sie brauchst

Was ist ein SBOM?

SBOM = Software Bill of Materials = „Zutatenliste“ für dein Container-Image

Stell dir vor:

  • Du kaufst ein Fertiggericht
  • KEINE Zutatenliste
  • KEIN MHD (Mindesthaltbarkeitsdatum)
  • Du weißt nicht, ob Allergene drin sind

Würdest du das essen? Wahrscheinlich nicht.

Aber genau DAS machst du mit Containern ohne SBOM! 🤯


SBOM generieren mit Syft:

Syft ist besser als Trivy’s SBOM für detaillierte Analysen:

# Syft installieren
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh

# SBOM im CycloneDX-Format
syft myapp:latest -o cyclonedx-json > sbom.json

# SBOM im SPDX-Format
syft myapp:latest -o spdx-json > sbom-spdx.json

# Human-readable Table
syft myapp:latest -o table

Output-Beispiel:

NAME                    VERSION      TYPE
spring-boot-starter-web 3.2.0        java-archive
spring-core             6.1.0        java-archive
tomcat-embed-core       10.1.15      java-archive
jackson-databind        2.15.3       java-archive  ← Hat CVEs!
alpine-baselayout       3.4.3-r1     apk
musl                    1.2.4-r1     apk
libcrypto3              3.1.2-r0     apk           ← OpenSSL!
...

Jetzt siehst du GENAU:

  • Welche Java-Dependencies drin sind
  • Welche OS-Packages
  • Exakte Versionen
  • Welche davon Vulnerabilities haben

SBOM in der Pipeline:

# GitHub Actions
- name: Generate SBOM with Syft
  run: |
    syft ${{ env.IMAGE_NAME }}:${{ github.sha }} \
      -o cyclonedx-json=sbom.json
    
    # SBOM als Artifact speichern
    echo "SBOM generated: sbom.json"

- name: Upload SBOM
  uses: actions/upload-artifact@v3
  with:
    name: sbom-${{ github.sha }}
    path: sbom.json

# SBOM scannen mit Grype
- name: Scan SBOM for Vulnerabilities
  run: |
    grype sbom:./sbom.json \
      --fail-on critical \
      --output table

Warum SBOM UND Vulnerability-Scan?

  1. Transparency: Du weißt, was in Production läuft
  2. Compliance: Viele Regulierungen verlangen SBOMs
  3. Supply-Chain-Security: Trackbar bei Incidents
  4. Audit-Trail: „Welche Version von Log4j hatten wir am 10. Dezember 2021?“

🏔️ Distroless Images: Das Next Level

Was ist Distroless?

Distroless = Container OHNE Distribution

Was fehlt?

  • ❌ Kein Shell (sh, bash)
  • ❌ Kein Package-Manager (apt, apk)
  • ❌ Keine System-Utilities (ls, cat, wget)
  • ❌ Nur Application + Runtime

Was bleibt?

  • ✅ Nur die JRE
  • ✅ Nur deine App
  • ✅ Minimal Required Libraries

Alpine vs. Distroless Vergleich:

# Alpine (wie in Teil 5)
FROM eclipse-temurin:21-jre-alpine
COPY app.jar .
USER javaapp
CMD ["java", "-jar", "app.jar"]

# Image-Size: ~120 MB
# Packages: 14
# CVEs: 0-5 (abhängig von Updates)
# Shell: JA (sh, ash)
# Distroless
FROM gcr.io/distroless/java21-debian12:nonroot
COPY app.jar /app/app.jar
WORKDIR /app
CMD ["app.jar"]

# Image-Size: ~75 MB
# Packages: 0 (im klassischen Sinne)
# CVEs: 0-2 (extrem selten)
# Shell: NEIN!

Vorteile Distroless:

  • ✅ 40% kleiner
  • ✅ Weniger Vulnerabilities
  • ✅ Kein Shell = Kein RCE (Remote Code Execution)
  • ✅ Minimale Attack Surface

Nachteile Distroless:

  • ❌ Debugging schwieriger (kein Shell!)
  • docker exec funktioniert nicht
  • ❌ Manche Tools fehlen

Optimiertes Dockerfile mit Distroless:

# =============================================================================
# Stage 1: Dependencies (wie gehabt)
# =============================================================================
FROM eclipse-temurin:21-jdk-alpine AS dependencies
WORKDIR /app
COPY pom.xml .
COPY .mvn .mvn
COPY mvnw .
RUN chmod +x mvnw
RUN ./mvnw dependency:go-offline -B

# =============================================================================
# Stage 2: Build (wie gehabt)
# =============================================================================
FROM dependencies AS build
WORKDIR /app
COPY src ./src
RUN ./mvnw package -DskipTests -B

# =============================================================================
# Stage 3: Runtime mit Distroless (NEU!)
# =============================================================================
FROM gcr.io/distroless/java21-debian12:nonroot

# Metadata
LABEL maintainer="Code Sentinel <code.sentinel@javafleet.de>"
LABEL org.opencontainers.image.source="https://github.com/java-fleet/container-security-demo"
LABEL org.opencontainers.image.description="Container Security Demo - Teil 6"

WORKDIR /app

# JAR kopieren
COPY --from=build /app/target/*.jar app.jar

# JVM-Optimierungen
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport \
                        -XX:MaxRAMPercentage=75.0 \
                        -Djava.security.egd=file:/dev/./urandom"

# Port (Dokumentation)
EXPOSE 8080

# WICHTIG: Distroless läuft bereits als non-root!
# User: nonroot (UID 65532)
# Kein "USER" Command nötig!

# CMD statt ENTRYPOINT (Distroless empfiehlt CMD)
CMD ["app.jar"]

# =============================================================================
# SECURITY-VERBESSERUNGEN vs. Alpine:
# =============================================================================
# ✅ Kein Shell → Kein RCE bei Code-Injection
# ✅ Kein Package-Manager → Keine Post-Deploy-Änderungen
# ✅ Minimal Libraries → Weniger CVEs
# ✅ Bereits non-root (UID 65532)
# ✅ Image-Size: 75 MB statt 120 MB
#
# =============================================================================
# DEBUG-ALTERNATIVE (für Entwicklung):
# =============================================================================
# FROM gcr.io/distroless/java21-debian12:debug-nonroot
# Enthält busybox für debugging
# NUR für Development/Staging, NICHT Production!
# =============================================================================

Was macht das konkret?

  1. Base-Image: gcr.io/distroless/java21-debian12:nonroot
    • Google’s Distroless mit Java 21
    • Debian 12 Base (aber OHNE Distribution-Tools!)
    • :nonroot = läuft als UID 65532
  2. Kein Shell:
    • docker exec -it container sh → FEHLER
    • Gut! Shell ist häufigster RCE-Vector
  3. Nur Java + App:
    • Keine Utilities
    • Keine zusätzlichen Libraries
    • Minimal Required Only

Distroless Debug-Variant (für Development):

# Für Entwicklung/Staging:
FROM gcr.io/distroless/java21-debian12:debug-nonroot

# Enthält busybox für debugging:
# - ls, cat, ps, top, ...
# - Hilfreich für Troubleshooting

# Für Production:
FROM gcr.io/distroless/java21-debian12:nonroot
# KEIN busybox!

Best Practice:

  • Development/Staging: :debug-nonroot
  • Production: :nonroot (ohne debug)

🔬 Container-Härtung: Security Best Practices

1. Multi-Stage Build Layers optimieren:

# FALSCH: Secrets in Build-Stage
FROM maven:3.9-eclipse-temurin-21 AS build
ENV GITHUB_TOKEN=ghp_abc123...  # ❌ SECRET IM IMAGE!
RUN ./mvnw package

# RICHTIG: Secrets als Build-Arg
FROM maven:3.9-eclipse-temurin-21 AS build
ARG GITHUB_TOKEN  # Nur während Build verfügbar
RUN --mount=type=secret,id=github_token \
    GITHUB_TOKEN=$(cat /run/secrets/github_token) \
    ./mvnw package

# Build mit Secret:
docker build --secret id=github_token,src=token.txt .

Warum wichtig?

  • Secrets in Layers bleiben im Image
  • docker history zeigt alle Layers
  • Selbst gelöschte Secrets sind im Layer-History

2. .dockerignore erweitern:

# Aus Teil 5
target/
.git/
*.log

# NEU: Security-relevante Files
.env
.env.*
*.key
*.pem
*.p12
*.jks
secrets/
credentials/

# AWS-Configs
.aws/
aws-credentials

# SSH-Keys
.ssh/
id_rsa
id_dsa

# Certificates
*.crt (außer public certs!)
*.cer

# Database-Dumps
*.sql
*.dump

3. Read-Only Filesystem:

# In deinem Dockerfile:
# (Distroless macht das automatisch)

# Oder in docker-compose.yml:
services:
  app:
    image: myapp:latest
    read_only: true
    tmpfs:
      - /tmp
      - /app/logs

Was macht das?

  • Container-Filesystem ist read-only
  • App kann NICHT Files schreiben (außer /tmp, /app/logs)
  • Verhindert: Malware-Installation, Log-Manipulation

4. Security Context (Kubernetes):

# kubernetes-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      securityContext:
        # Pod-Level Security
        runAsNonRoot: true
        runAsUser: 65532  # Distroless nonroot UID
        fsGroup: 65532
        seccompProfile:
          type: RuntimeDefault
      
      containers:
      - name: myapp
        image: myapp:latest
        securityContext:
          # Container-Level Security
          allowPrivilegeEscalation: false
          readOnlyRootFilesystem: true
          capabilities:
            drop:
              - ALL  # Drop ALLE Linux Capabilities
          runAsNonRoot: true
        
        volumeMounts:
        - name: tmp
          mountPath: /tmp
        
      volumes:
      - name: tmp
        emptyDir: {}

Was macht das konkret?

  1. runAsNonRoot: true
    • Container MUSS als non-root laufen
    • Falls root-User: Pod wird nicht gestartet
  2. allowPrivilegeEscalation: false
    • Verhindert Privilege-Escalation
    • User kann nicht zu root werden
  3. capabilities drop ALL
    • Entfernt alle Linux Capabilities
    • App hat nur minimal Required Permissions
  4. readOnlyRootFilesystem
    • Filesystem read-only
    • Nur /tmp beschreibbar (via Volume)

🚀 Pipeline-Integration: Complete Security-Stack

GitHub Actions Workflow (erweitert):

name: Container Security Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
  schedule:
    # Wöchentlicher Security-Scan (auch ohne Code-Changes!)
    - cron: '0 2 * * 1'  # Jeden Montag 02:00 UTC

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  security-scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write  # Für SARIF-Upload

    steps:
      # =======================================================================
      # Standard Setup
      # =======================================================================
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # =======================================================================
      # Step 1: Build Multi-Stage Image
      # =======================================================================
      - name: Build Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          load: true  # Lade Image lokal (für Scans)
          tags: ${{ env.IMAGE_NAME }}:scan
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max

      # =======================================================================
      # Step 2: Generate SBOM mit Syft
      # =======================================================================
      - name: Generate SBOM
        uses: anchore/sbom-action@v0
        with:
          image: ${{ env.IMAGE_NAME }}:scan
          format: cyclonedx-json
          output-file: sbom.json

      - name: Upload SBOM as Artifact
        uses: actions/upload-artifact@v3
        with:
          name: sbom-${{ github.sha }}
          path: sbom.json
          retention-days: 90  # 90 Tage aufbewahren

      # =======================================================================
      # Step 3: Vulnerability Scan mit Trivy (Comprehensive)
      # =======================================================================
      - name: Run Trivy Vulnerability Scanner
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.IMAGE_NAME }}:scan
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH,MEDIUM'
          # Fail Pipeline bei CRITICAL
          exit-code: '1'
          ignore-unfixed: true  # Ignoriere CVEs ohne Fix

      - name: Upload Trivy Results to GitHub Security
        uses: github/codeql-action/upload-sarif@v2
        if: always()  # Upload auch bei Failure
        with:
          sarif_file: 'trivy-results.sarif'

      # =======================================================================
      # Step 4: Secret Scanning mit Trivy
      # =======================================================================
      - name: Scan for Secrets in Image
        run: |
          trivy image \
            --scanners secret \
            --severity HIGH,CRITICAL \
            --exit-code 1 \
            ${{ env.IMAGE_NAME }}:scan

      # =======================================================================
      # Step 5: Dockerfile Best Practices Check
      # =======================================================================
      - name: Scan Dockerfile with Trivy
        run: |
          trivy config Dockerfile \
            --exit-code 0 \
            --severity MEDIUM,HIGH,CRITICAL

      # =======================================================================
      # Step 6: Grype Scan (Alternative zu Trivy)
      # =======================================================================
      - name: Scan SBOM with Grype
        run: |
          # Grype installieren
          curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh
          
          # SBOM scannen
          grype sbom:./sbom.json \
            --fail-on critical \
            --output table

      # =======================================================================
      # Step 7: Image-Size Check
      # =======================================================================
      - name: Check Image Size
        run: |
          SIZE=$(docker inspect ${{ env.IMAGE_NAME }}:scan \
            --format='{{.Size}}' | awk '{print $1/1024/1024}')
          
          echo "Image Size: ${SIZE} MB"
          
          # Fail wenn > 200 MB (für Distroless sollte <100 MB sein!)
          if (( $(echo "$SIZE > 200" | bc -l) )); then
            echo "❌ Image zu groß! Max: 200 MB, Ist: ${SIZE} MB"
            exit 1
          fi
          
          echo "✅ Image-Size OK: ${SIZE} MB"

      # =======================================================================
      # Step 8: Push Image (nur wenn alle Checks OK!)
      # =======================================================================
      - name: Push Image to Registry
        if: success()
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache

      # =======================================================================
      # Step 9: Generate Security Report
      # =======================================================================
      - name: Generate Security Summary
        if: always()
        run: |
          echo "## 🔐 Security Scan Results" >> $GITHUB_STEP_SUMMARY
          echo "" >> $GITHUB_STEP_SUMMARY
          
          # SBOM Stats
          TOTAL_PACKAGES=$(jq '.components | length' sbom.json)
          echo "**SBOM Generated:** $TOTAL_PACKAGES packages tracked" >> $GITHUB_STEP_SUMMARY
          
          # Trivy Results
          if [ -f trivy-results.sarif ]; then
            CRITICAL=$(jq '.runs[0].results | map(select(.level=="error")) | length' trivy-results.sarif)
            echo "**Vulnerabilities:** $CRITICAL CRITICAL found" >> $GITHUB_STEP_SUMMARY
          fi
          
          echo "" >> $GITHUB_STEP_SUMMARY
          echo "✅ All security checks passed!" >> $GITHUB_STEP_SUMMARY

# =============================================================================
# SECURITY-PIPELINE ERKLÄRT:
# =============================================================================
#
# 1. Build Image (wie in Teil 5)
# 2. Generate SBOM (Transparency!)
# 3. Vulnerability Scan (Trivy comprehensive)
# 4. Secret Scan (keine hardcoded Secrets)
# 5. Dockerfile Check (Best Practices)
# 6. Alternative Scan mit Grype
# 7. Image-Size Check (nicht zu groß!)
# 8. Push nur wenn ALLE Checks OK
# 9. Security-Report generieren
#
# WICHTIG:
# - Wöchentlicher Scan (auch ohne Code-Changes!)
# - SBOM wird 90 Tage aufbewahrt
# - SARIF-Upload zu GitHub Security
# - Pipeline failet bei CRITICAL CVEs
#
# =============================================================================

💡 Real Talk: Das Team bemerkt was

Java Fleet Küche, 12:30 Uhr. Kofi holt sich Kaffee, Elyndra und Nova sitzen am Tisch.


Nova: „Leute, hab ich das richtig gesehen? Code hat heute im Stand-up GELACHT? Wie… richtig gelacht, nicht nur höflich gegrinst?“

Elyndra: grinst „Und er hat mein ‚Quick-and-Dirty‘-Deployment nicht kritisiert. Normalerweise kommt sofort: ‚Rollback-Plan? Monitoring? Incident-Response-Strategie?'“

Kofi: „Yo, gestern kam ich zu ihm mit einer Frage. Ich dachte, er macht wieder seine 45-Minuten Risk-Assessment-Session. Stattdessen: ‚Kofi, deploy es einfach. Wir haben Monitoring. Worst-Case? Rollback in 30 Sekunden. Chill.‘ CHILL! Code Sentinel hat CHILL gesagt!“

Nova: lacht „Wer ist das und was hat er mit unserem Code Sentinel gemacht?“

Kat: kommt dazu mit Pixel (ihrem Hund) „Über wen redet ihr?“

Elyndra: „Code. Er ist… anders. Entspannter. Fast schon… menschlich?“ zwinkert

Kat: „Ah! Das hat Jamal auch erwähnt. Code hat letzte Woche ein Meeting um 18:00 Uhr ABGELEHNT! Er meinte: ‚Sorry, ich hab was vor. Kann das bis morgen warten?‘ Normalerweise arbeitet er bis 22:00 Uhr!“

Nova: flüstert verschwörerisch „Ich wette, das hat was mit seiner Nachbarin zu tun. Lisa, oder?“

Kofi: „Die Grundschullehrerin? Die mit den Katzen?“

Elyndra: „Mhm. Neulich hab ich ihn im Flur gesehen. Er hat… GELÄCHELT. Einfach so. Ohne Grund. Also… vielleicht gab’s einen Grund.“ grinst

Kat: „Awww! Code Sentinel hat einen Crush! Das ist ja süß!“

Nova: „Süß? Das ist ein WUNDER! Der Mann hat sein Leben nach Checklisten organisiert. ‚Rollback-Plan für Gefühle‘ und so.“

Elyndra: lacht „Vielleicht hat er gelernt, dass nicht alles planbar ist. Manche Dinge… passieren einfach.“

Kofi: „Real talk: Es tut ihm gut. Er ist immer noch der Security-Paranoia-König, aber… lockerer. Als würde er endlich verstehen, dass ‚gut genug‘ manchmal besser ist als ‚perfekt aber nie fertig‘.“

Kat: „Sollten wir ihn drauf ansprechen?“

Elyndra: „Bloß nicht! Lass ihn. Wenn er bereit ist, erzählt er’s uns. Oder auch nicht. Ist okay.“

Nova: „Aber ich bin SO neugierig!“

Kofi: lacht „Nova, du bist immer neugierig. Lass Code sein Ding machen. Solange er happy ist…“

Alle: nicken zustimmend

Elyndra: „Okay, genug getuschelt. Back to work. Aber hey – es ist schön, Code glücklich zu sehen.“


Code Sentinel kommt um die Ecke, Kopfhörer auf den Ohren, summend.

Code: „Hey Leute! Schönen Mittag noch!“ winkt, geht weiter

Nova: flüstert „Hat er gerade… GESUMMT?!“

Kofi: „Yep. Das ist offiziell. Code Sentinel ist verliebt.“

Alle: grinsen


❓ FAQ

Q: Alpine oder Distroless – was soll ich nehmen?

A: Depends on your use-case:

Alpine wenn:

  • Du Debugging brauchst (Shell!)
  • Du zusätzliche Tools installieren musst
  • Dein Team Alpine gewohnt ist

Distroless wenn:

  • Production mit höchsten Security-Anforderungen
  • Du minimale Attack-Surface willst
  • Compliance es verlangt (NIST, CIS, …)

Code Sentinel’s Take: Ich nutze Alpine für Development/Staging, Distroless für Production. Best of both worlds.


Q: Muss ich WIRKLICH SBOMs generieren?

A: Ja. Seriously.

Warum:

  1. Log4Shell Dezember 2021: Unternehmen ohne SBOM brauchten WOCHEN um zu prüfen: „Haben wir Log4j? Welche Version?“
  2. Compliance: EU Cyber Resilience Act, Executive Order 14028, …
  3. Supply-Chain-Attacks: Du MUSST wissen, was in Production läuft

Real talk: SBOM-Generation dauert 30 Sekunden. Do it.


Q: Trivy findet 47 CVEs. Muss ich ALLE fixen?

A: Nein.

Priorisierung:

  1. CRITICAL + HIGH: Sofort fixen
  2. MEDIUM: Priorisieren, Fix planen
  3. LOW: Backlog, bei Gelegenheit

Aber:

  • --ignore-unfixed true: Ignoriere CVEs ohne verfügbaren Fix
  • Manche CVEs sind „theoretical“ (nicht praktisch exploitierbar)
  • Context matters: Ist der vulnerable Code überhaupt erreichbar?

Code Sentinel’s Rule: CRITICAL = Production-Blocker. HIGH = Fix innerhalb 7 Tage. MEDIUM = Nach Priorität.


Q: Distroless hat keinen Shell. Wie debugge ich?

A: Drei Optionen:

Option 1: Debug-Variant für Development

FROM gcr.io/distroless/java21-debian12:debug-nonroot

Option 2: Ephemeral Debug Container (Kubernetes)

kubectl debug -it mypod --image=busybox --target=mycontainer

Option 3: Logging & Observability

  • Strukturiertes Logging
  • Metrics (Prometheus)
  • Distributed Tracing
  • Kein Shell nötig!

Real talk: Wenn du Shell zum Debuggen brauchst, hast du ein Observability-Problem, kein Container-Problem.


Q: Kann ich meine eigenen Base-Images bauen?

A: Ja, aber…

Vorteile:

  • ✅ Volle Kontrolle
  • ✅ Custom Optimierungen
  • ✅ Firmen-Standards

Nachteile:

  • ❌ Du bist verantwortlich für Security-Updates
  • ❌ Maintenance-Overhead
  • ❌ Keine automatischen Patches

Code Sentinel’s Take: Nutze offizielle Images (eclipse-temurin, distroless). Lass Google/Eclipse Foundation die Security-Updates machen. Deine Zeit ist besser investiert in App-Security.


Q: Wie oft soll ich Images neu bauen wegen Security-Updates?

A: Mindestens wöchentlich, auch ohne Code-Changes!

Warum? Base-Images bekommen ständig Security-Patches. Dein 3 Monate altes Image hat wahrscheinlich Vulnerabilities die längst gefixt sind.

Best Practice:

# GitHub Actions Schedule
on:
  schedule:
    - cron: '0 2 * * 1'  # Jeden Montag 02:00 UTC

Alternative: Renovate/Dependabot für automatische Base-Image-Updates.


Q: Was macht ihr bei persönlichen Problemen im Team?

A: Gute Frage… Real talk: Manche Geschichten passen nicht in Tech-Blogs. Die gehören eher zu private logs. Ein anderes Kapitel. 📖

Aber zwischen uns? Es tut gut, wenn das Team füreinander da ist. Auch außerhalb von Merge-Conflicts und Pipeline-Failures. Lisa hat mir das gezeigt – ich meine… gute Freunde halt. Ihr wisst schon. 😉


✅ Checkpoint: Hast du es geschafft?

Teste dein Wissen! Nach diesem Teil solltest du:

Grundlagen:

  • [ ] Verstehen warum Container-Security wichtig ist
  • [ ] SBOMs erklären können
  • [ ] Unterschied Alpine vs. Distroless kennen
  • [ ] Supply-Chain-Security-Konzepte verstehen

Praktisch:

  • [ ] SBOM mit Syft generieren
  • [ ] Trivy comprehensive Scan durchführen
  • [ ] Distroless Dockerfile schreiben
  • [ ] Secret-Scanning implementieren

Pipeline:

  • [ ] Complete Security-Pipeline aufbauen
  • [ ] SARIF zu GitHub Security hochladen
  • [ ] Wöchentliche Security-Scans schedulen
  • [ ] Image-Size Gates implementieren

Security:

  • [ ] Read-Only Filesystem nutzen
  • [ ] Security Context in Kubernetes
  • [ ] Capability-Dropping verstehen
  • [ ] Image härten für Production

Test:

# 1. SBOM generieren
syft myapp:latest -o cyclonedx-json > sbom.json

# 2. Comprehensive Security-Scan
trivy image myapp:latest --severity CRITICAL,HIGH

# 3. Secret-Scan
trivy image --scanners secret myapp:latest

# 4. Dockerfile-Check
trivy config Dockerfile

# 5. Image-Size prüfen
docker images | grep myapp
# Sollte <100 MB sein (Distroless)

# 6. User prüfen
docker run --rm myapp whoami
# Sollte sein: nonroot (UID 65532)

Wenn alles ✅ ist: Du bist Production-Security-ready! 🎉

Wenn etwas ❌ ist: Check das Maven-Projekt im Download-Bereich!


📦 Downloads & Ressourcen

Komplettes Maven-Projekt:

  • 📂 container-security-demo.zip – Vollständiges Projekt mit:
    • Distroless Dockerfile
    • Alpine Dockerfile (zum Vergleich)
    • Complete Security Pipeline (GitHub Actions)
    • SBOM-Generation Setup
    • Example Java-App
    • Security-Scripts
    • README mit Setup

Templates:

  • 📄 Dockerfile-Distroless-Template.txt
  • 📄 security-pipeline.yml – Complete GitHub Actions
  • 📄 kubernetes-securitycontext.yaml – K8s Security
  • 📄 trivy-config.yaml – Trivy Configuration

Scripts:

  • 🔧 generate-sbom.sh – SBOM-Generation Script
  • 🔧 security-scan.sh – Local Security-Scan
  • 🔧 compare-images.sh – Alpine vs. Distroless Vergleich

Weitere Resources:


🎯 Community-Challenge

Diese Woche:

Migriere ein bestehendes Alpine-Image auf Distroless und vergleiche!

Challenge:

  1. Nimm dein Image aus Teil 5
  2. Erstelle Distroless-Variant
  3. Generiere SBOMs für beide
  4. Vergleiche:
    • Image-Size
    • Vulnerability-Count
    • Build-Time
    • Complexity
  5. Teile Ergebnisse mit #JavaFleetSecurity

Bonus-Punkte:

  • Image <100 MB: ⭐
  • Zero CRITICAL CVEs: ⭐⭐
  • Complete Security-Pipeline: ⭐⭐⭐

🔮 Nächste Woche: Teil 7

Titel: „Von Docker Hub zu Private Registry – Image-Management richtig“

Was dich erwartet:

  • GitHub Container Registry (GHCR)
  • Private Registry Setup
  • Image-Tagging-Strategien
  • Multi-Architecture Builds (AMD64, ARM64)
  • Registry-Security & Access-Control

Warum wichtig: Dein Image ist sicher – aber wo speicherst du es? Docker Hub? GHCR? Private Registry? Nächste Woche zeige ich dir, wie Image-Management in echten Projekten funktioniert.

Tipp für diese Woche: Experimentiere mit Distroless! Starte mit :debug-nonroot für Development, dann wechsel zu :nonroot für Production. Spüre den Security-Boost!


🎓 Serie-Übersicht: Alle 12 Teile

Modul 1: Foundations (Teil 1-4)

  1. Erste Pipeline ✅
  2. Security Gates (OWASP & Trivy Basic) ✅
  3. Coverage Gates (JaCoCo) ✅
  4. Quality Gates (SonarQube) ✅

Modul 2: Docker & Container (Teil 5-7) 🔄 5. Multi-Stage Builds ✅ 6. Container Security & SBOMDU BIST HIER 7. Registry Integration ⏳ Nächste Woche

Modul 3: Deployment (Teil 8-10) ⏳ 8. Blue-Green Deployments 9. Canary Releases & Kubernetes 10. GitOps

Modul 4: Enterprise (Teil 11-12) ⏳ 11. Jenkins Enterprise 12. Multi-Platform & Finale


🤝 Danke fürs Lesen!

Hat dir dieser Security-Deep-Dive geholfen?

Real talk: Security ist nicht optional. Es ist die Grundlage von allem. Aber es muss nicht kompliziert sein. Distroless + SBOM + gute Pipeline = Production-ready.

Kontakt:

Feedback & Fragen:

  • 👍 Hat dir der neue Ton gefallen?
  • 💡 Mehr Security-Themen gewünscht?
  • 🐛 Fehler gefunden? Schreib mir!

🔐 Bernd’s Corner

Bernd ist unser mysteriöser Remote-Senior-Developer. Er committet nur nachts und hat noch nie ein Video-Call gemacht.

Bernd’s Kommentar zu diesem Teil:

„Distroless ist okay. Aber habt ihr NixOS-Container probiert? Deterministic, reproducible, functional. Aber naja, für die meisten reicht wohl Google’s Zeug. Security Theater ist besser als kein Theater, schätze ich.“

– Bernd, 4:23 AM, via Slack DM

Code Sentinel’s Reaktion:

Danke Bernd. Immer konstruktiv. 🙄

Real talk: Distroless ist Industry-Standard und funktioniert hervorragend. Nix ist cool für spezielle Use-Cases, aber Overhead für die meisten.

Security ist kein Theater. Security ist die Grundlage. Punkt.


P.S.: Wer auch immer getuschelt hat in der Küche vorhin – ich hab euch gehört. Ja, ich war beschäftigt letzte Woche. Nein, ich erzähl’s euch noch nicht. Aber… vielleicht bald. Es gehört zu private logs. 😉


Bis nächste Woche! 🛡️

Code Sentinel
Technical Project Manager
Java Fleet Systems Consulting

(Und ja, ich bin glücklich. Danke der Nachfrage. 💕)


CI/CD Mastery Serie – Teil 6 von 12
© 2025 Java Fleet Systems Consulting
java-developer.online

Autor

  • Jamal Hassan

    💻 Luca Santoro – Der IT-Ninja

    IT-Support & DevOps Assistant | 29 Jahre | „Ich löse Dinge, bevor sie eskalieren.“

    Wenn bei Java Fleet der Drucker spinnt, das WLAN streikt oder der neue Mitarbeiter kein VPN-Zugriff hat – ist Luca schon unterwegs.
    Er ist der stille Systemretter, der dafür sorgt, dass alle anderen arbeiten können.
    Luca ist nicht laut, nicht hektisch, nicht übertrieben heroisch.
    Er ist einfach da – und das rechtzeitig.

    Seit 2023 unterstützt er das Team im Bereich IT-Support, Netzwerkmanagement und DevOps-Automatisierung.
    Er ist das, was man in der IT selten findet: zuverlässig, gelassen und serviceorientiert – mit technischem Tiefgang und menschlicher Geduld.

    💻 Die Tech-Seite

    Luca denkt in Systemen, nicht in Symptomen.
    Er betreut Hardware, richtet Arbeitsplätze ein, pflegt Benutzerkonten, überwacht Backups und unterstützt das DevOps-Team bei kleineren Deployments.
    Er arbeitet mit Linux, Docker, Active Directory, Ansible und klassischen Office-Netzwerkstrukturen.

    Was ihn besonders macht: Er versteht, dass IT-Support nicht nur Technik ist, sondern Kommunikation.
    Er erklärt, was er tut – klar, freundlich, ohne Fachjargon.
    Und er schreibt sich jeden Fehler auf, um ihn beim nächsten Mal schneller zu beheben.

    „Ich bin kein Feuerwehrmann. Ich bin der, der Rauchmelder installiert.“

    Wenn Franz-Martin über Stabilität redet, meint er oft Systeme, die Luca im Hintergrund pflegt.
    Er hat ein gutes Auge für Details, liebt klare Strukturen und hält Ordnung, wo Chaos droht.

    🌿 Die menschliche Seite

    Luca hat italienisch-deutsche Wurzeln, liebt guten Espresso und hat die entspannte Art eines Menschen, der Probleme ernst nimmt, aber nie dramatisch macht.
    Er ist höflich, hilfsbereit und humorvoll – der Typ Kollege, der leise lacht, wenn etwas schiefläuft, und sagt:

    „Kein Stress. Ich schau’s mir kurz an.“

    Er trägt oft ein leichtes Headset, hört Musik beim Arbeiten und findet in kleinen Routinen seinen Flow.
    Wenn andere nach Feierabend den Laptop zuklappen, prüft er noch schnell den Serverstatus – „nur zur Sicherheit“.

    Cassian sagt: „Er ist die Ruhe im System.“
    Kat nennt ihn „unseren stillen Lifesaver“.
    Und Franz-Martin beschreibt ihn mit einem Augenzwinkern:

    „Luca ist der Grund, warum der Kaffeeautomat läuft – und unser Git-Server auch.“

    🧠 Seine Rolle im Team

    Luca ist das stille Fundament der Java Fleet – derjenige, der sicherstellt, dass alle Systeme, Geräte und Prozesse ineinandergreifen.
    Er ist kein Entwickler im klassischen Sinne, aber ohne ihn gäbe es keine funktionierende Umgebung für die, die entwickeln.
    Seine Arbeit bleibt meist unsichtbar, doch seine Wirkung ist täglich spürbar.

    Er ist der Ansprechpartner, wenn Probleme auftauchen – und noch lieber: bevor sie auftauchen.
    Er dokumentiert, strukturiert, denkt voraus – und erinnert das Team daran, dass Zuverlässigkeit kein Feature ist, sondern eine Haltung.

    ⚡ Superkraft

    Ruhe im System.
    Luca erkennt Fehler, bevor sie eskalieren – und löst sie, bevor sie jemand bemerkt.

    💬 Motto

    „Wenn’s funktioniert und keiner weiß warum – dann hab ich’s repariert.“