Zum Hauptinhalt springen
S-EDV news
← Alle Anleitungen
📘 Anleitung Docker 04.06.2026 · 10 min Lesezeit

Docker-Images auf Schwachstellen scannen mit Trivy: CVE-Check, SBOM und automatische Scans im KMU-Workflow

Trivy scannt Docker-Images auf CVEs, erzeugt SBOM für NIS2/DSGVO und lässt sich per Cron für laufende Container automatisieren – kostenlos und breiter als Grype oder Docker Scout. Diese Anleitung zeigt den kompletten KMU-Workflow.

Digitaler Security-Scanner analysiert Docker-Container auf Schwachstellen

Wer Docker-Images in Produktion betreibt, trägt die Verantwortung für bekannte Sicherheitslücken in allen enthaltenen Paketen – auch wenn das Base-Image vom offiziellen Hub stammt. Trivy von Aqua Security ist der de-facto Standard für Open-Source-Container-Scanning: eine einzelne Go-Binärdatei ohne Daemon, die CVEs in OS-Paketen und Sprachlaufzeiten findet, Secrets aufdeckt, Fehlkonfigurationen prüft und vollständige SBOM-Dokumente für Compliance-Nachweise exportiert. Diese Anleitung zeigt, wie du Trivy sinnvoll in den KMU-Alltag integrierst – nicht als einmaligen Build-Check, sondern als kontinuierlichen Prozess.

Voraussetzungen

  • Docker oder Podman installiert und lauffähig (Images müssen lokal vorhanden oder pullbar sein)
  • Trivy-Binärdatei: brew install trivy (macOS), apt install trivy via Aqua-Repo (Debian/Ubuntu) oder direkter Download von github.com/aquasecurity/trivy/releases
  • Internetzugang für den ersten DB-Download von ghcr.io/aquasecurity/trivy-db (oder interner OCI-Registry-Mirror für Air-Gapped-Umgebungen)
  • CI/CD-System (GitHub Actions, GitLab CI, Jenkins) für automatisierte Pipeline-Integration – optional, aber empfohlen
  • Archivspeicher für SBOM-Dateien und Scan-Logs (NAS, S3 mit Object Lock oder Artefakt-Registry wie Harbor/Nexus)
  • Optional: Harbor oder Nexus als interner Registry-Mirror für DB-Updates und Air-Gapped-Betrieb

Schritt 1: Erster Scan – Severity richtig konfigurieren

Der einfachste Trivy-Befehl ist trivy image nginx – er gibt aber so viele LOW- und MEDIUM-Findings aus, dass der Überblick sofort verloren geht. Für KMU-Umgebungen gilt: CI/CD-Break nur bei HIGH und CRITICAL, LOW/MEDIUM nur in Reports ohne Exit-Code 1. Das Flag --ignore-unfixed blendet Schwachstellen ohne verfügbaren Patch aus und reduziert das Rauschen erheblich.

# Basis-Scan mit Severity-Filter und CI/CD-Exit-Code
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest

# Mit --ignore-unfixed: nur fixbare Schwachstellen zeigen
trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 myapp:latest

Trivy gibt Exit-Code 1 zurück, sobald mindestens ein Finding der konfigurierten Severity-Stufen gefunden wird – das bricht eine Pipeline zuverlässig ab. Ohne --exit-code 1 endet Trivy immer mit Exit-Code 0, auch wenn kritische Lücken vorhanden sind.

Die Vulnerability-Datenbank wird beim ersten Scan automatisch heruntergeladen (~/.cache/trivy). Sie wird alle 6 Stunden von ghcr.io/aquasecurity/trivy-db aktualisiert. Für Java-Images brauchst du zusätzlich die separate trivy-java-db – dazu mehr im Troubleshooting-Abschnitt.

Schritt 2: .trivyignore.yaml – Risikoakzeptanz mit Audit-Nachweis

Manche CVEs lassen sich kurzfristig nicht beheben: der Upstream-Patch fehlt, das Base-Image wird erst im nächsten Sprint aktualisiert, oder die Schwachstelle ist im konkreten Deployment nicht erreichbar. Statt diese Findings dauerhaft zu ignorieren oder den Scan abzuschalten, dokumentierst du die bewusste Risikoakzeptanz in einer .trivyignore.yaml mit Begründung (statement) und Ablaufdatum (expired_at).

Wichtig: YAML-basierte Ignore-Dateien werden von Trivy nicht automatisch erkannt – du musst --ignorefile .trivyignore.yaml explizit übergeben. Die Plain-Text-Datei .trivyignore (ohne Erweiterung) wird hingegen automatisch geladen, bietet aber keine Dokumentationsfelder.

# .trivyignore.yaml (YAML-Format mit vollständiger Dokumentation)
vulnerabilities:
  - id: CVE-2023-3817
    statement: "OpenSSL-Timing-Angriff, kein öffentlicher Endpunkt exponiert, akzeptiert bis Upstream-Patch"
    expired_at: 2025-06-30
    paths:
      - "usr/local/lib/python3.11"

  - id: CVE-2024-5678
    statement: "Nur in Dev-Images, nie in Produktion deployed"
    expired_at: 2025-09-01

misconfigurations:
  - id: AVD-DS-0001
    statement: "Absichtlich kein HEALTHCHECK in diesem Sidecar-Container"
# Trivy mit .trivyignore.yaml aufrufen
trivy image --severity HIGH,CRITICAL \
  --ignorefile .trivyignore.yaml \
  --ignore-unfixed \
  --exit-code 1 \
  myapp:latest

# Suppressed Findings trotzdem anzeigen (für Audit-Logs)
trivy image --severity HIGH,CRITICAL \
  --ignorefile .trivyignore.yaml \
  --show-suppressed \
  myapp:latest

Das expired_at-Feld ist entscheidend für den KMU-Alltag: Sobald das Datum abgelaufen ist, taucht das Finding wieder im Scan-Ergebnis auf und erzwingt eine Neu-Bewertung. So entsteht kein „Friedhof" vergessener Ausnahmen, der im nächsten Audit unangenehm auffällt.

Schritt 3: SBOM für NIS2 und DSGVO erzeugen

Ein Software Bill of Materials (SBOM) listet alle enthaltenen Komponenten eines Images – inklusive Versionen und bekannter CVEs. Für NIS2- und DSGVO-Nachweise brauchst du dieses Dokument regelmäßig und revisionssicher archiviert, nicht nur einmalig.

# SBOM als CycloneDX JSON (inkl. CVE-Daten) – für NIS2/DSGVO empfohlen
trivy image --scanners vuln --format cyclonedx --output sbom-cyclonedx.json myapp:latest

# SBOM als SPDX JSON (breitere Toolchain-Kompatibilität)
trivy image --format spdx-json --output sbom-spdx.json myapp:latest

# SBOM als SPDX Tag-Value
trivy image --format spdx --output sbom.spdx myapp:latest

# Mit Datum und Digest im Dateinamen archivieren
DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' myapp:latest | cut -d@ -f2 | cut -c1-12)
trivy image --scanners vuln --format cyclonedx \
  --output "sbom-myapp-$(date +%Y%m%d)-${DIGEST}.json" \
  myapp:latest

Kritischer Fallstrick: trivy image --format cyclonedx ohne --scanners vuln erzeugt nur ein Paketinventar ohne CVE-Verlinkungen. Immer beide Flags kombinieren, wenn du ein Compliance-taugliches SBOM mit Schwachstellendaten benötigst.

Für revisionssichere Archivierung empfiehlt sich S3 mit Object Lock, ein NAS mit WORM-Konfiguration oder eine Artefakt-Registry (Harbor, Nexus) mit konfigurierten Retention-Policies. Dateiname immer mit Datum und Image-Digest versehen – so ist der gescannte Stand eindeutig nachvollziehbar.

Schritt 4: Laufende Container täglich rescannen per Cron

Ein Build-Zeit-Scan ist nur eine Momentaufnahme. Neue CVEs erscheinen täglich – ein Image, das beim Build sauber war, kann zwei Wochen später kritische Lücken haben. Für laufende Produktions-Container brauchst du deshalb regelmäßige Rescans, unabhängig vom Deployment-Zyklus.

#!/bin/bash
# /usr/local/bin/trivy-rescan.sh
# Täglich alle laufenden Container-Images scannen

LOGDIR=/var/log/trivy-scans
mkdir -p "$LOGDIR"

# DB vor dem Scan aktualisieren
trivy image --download-db-only

for IMAGE in $(docker ps --format '{{.Image}}' | sort -u); do
  SAFE_NAME=$(echo "$IMAGE" | tr '/:' '__')
  trivy image --severity HIGH,CRITICAL --ignore-unfixed \
    --format json \
    --output "${LOGDIR}/${SAFE_NAME}-$(date +%Y%m%d).json" \
    "$IMAGE"
done
# /etc/cron.d/trivy-rescan
# Täglich um 03:00 Uhr, Ergebnisse nach /var/log/trivy-scans/
0 3 * * * root /usr/local/bin/trivy-rescan.sh >> /var/log/trivy-rescan.log 2>&1

Die JSON-Ausgabe lässt sich direkt in ein SIEM (Graylog, Elastic) oder ein Monitoring-System wie Grafana mit Loki einspeisen. So bekommst du Alerts bei neuen HIGH/CRITICAL-Findings, ohne täglich Logs manuell zu prüfen. Eine passende Grundlage für zentrales Logging bietet die Anleitung Logs zentralisieren mit Grafana Loki.

Schritt 5: GitHub Actions Integration mit SARIF

Für Teams mit GitHub als CI/CD-Plattform gibt es die offizielle trivy-action. Das SARIF-Format erlaubt die direkte Visualisierung im GitHub-Security-Tab und in Pull-Request-Kommentaren.

# .github/workflows/trivy-scan.yml (Ausschnitt)
- name: Trivy vulnerability scan
  uses: aquasecurity/trivy-action@a20de5420d57c4102486cdd9349b532415ad09e3  # Commit-SHA pinnen!
  with:
    image-ref: 'myapp:latest'
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    ignore-unfixed: true
    trivyignores: '.trivyignore.yaml'

- name: Upload SARIF to GitHub Security Tab
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: 'trivy-results.sarif'

Sicherheitshinweis: Die trivy-action sollte immer auf einen spezifischen Commit-SHA gepinnt werden – nicht auf @master oder einen Tag allein. Im März 2026 wurden Trivy-GitHub-Actions-Tags kompromittiert; gepinnte SHAs sind gegen diesen Angriffsvektor resistent. Den aktuellen SHA findest du im offiziellen Repository.

Schritt 6: Offline-Betrieb und DB-Caching

In Air-Gapped-Umgebungen oder bei Pipelines mit vielen parallelen Jobs musst du die Vulnerability-Datenbank lokal vorhalten, statt sie bei jedem Scan herunterzuladen.

# DB einmalig herunterladen (speichert nach ~/.cache/trivy)
trivy image --download-db-only

# Java-DB separat herunterladen (Pflicht für Maven/Gradle-Images)
trivy image --download-java-db-only

# Folge-Scans ohne DB-Update
trivy image --skip-db-update --severity HIGH,CRITICAL myapp:latest

# Alternativer DB-Mirror (z.B. interner Harbor-Mirror)
trivy image --db-repository harbor.intern.example.com/trivy/trivy-db:2 myapp:latest

# DB-Cache-Pfad anpassen (z.B. für CI-Artifact-Caching)
trivy image --cache-dir /opt/trivy-cache --skip-db-update myapp:latest

In CI/CD-Systemen empfiehlt sich ein dedizierter Cache-Job, der die DB einmal herunterlädt und als Pipeline-Artefakt weiterschreibt. Alle anderen Jobs lesen aus dem Cache mit --skip-db-update. Das verhindert das Rate-Limit-Problem von ghcr.io bei parallelen Runs.

Trivy vs. Grype vs. Docker Scout – Entscheidungshilfe

KriteriumTrivyGrypeDocker Scout
KostenKostenlos (Apache 2.0)Kostenlos (Apache 2.0)Kostenlos nur für Einzelpersonen; ab Docker Pro/Team ~5–9 USD/Monat/Nutzer
Scan-GeschwindigkeitMittel30–40 % schnellerMittel (cloud-basiert)
SBOM-ExportCycloneDX JSON, SPDX JSON, SPDX Tag-ValueCycloneDX, SPDXBegrenzt (SPDX)
EPSS/KEV-DatenNein (nur CVSS)Ja, ab v6 nativTeilweise
IaC/Kubernetes-ScanJa (Helm, Terraform, K8s)NeinNein
Secret-ScanJa (eingebaut)NeinNein
Offline-BetriebJa (vollständig)JaNein (cloud-abhängig)
GitHub-Integrationtrivy-action (SARIF)grype-actionDocker Desktop nativ
KMU-EmpfehlungErste WahlWenn Scan-Speed kritischNur bei bestehendem Docker-Hub-Abo

Troubleshooting / Typische Fehler

  • Veraltete DB (bekannte CVEs werden nicht gemeldet): Tritt auf nach --skip-db-update oder wenn der Cache älter als 24 Stunden ist. Lösung: trivy image --download-db-only explizit vorab ausführen; CI-Cache-TTL auf maximal 12 Stunden begrenzen.
  • TOOMANYREQUESTS bei DB-Download: Bei vielen parallelen CI-Jobs überschreitet ghcr.io das anonyme Rate-Limit (~44.000 Req/min). Lösung: DB einmal als Pipeline-Artefakt herunterladen und cachen; alternativ --db-repository auf einen internen Mirror zeigen lassen.
  • CycloneDX ohne CVE-Daten: trivy image --format cyclonedx ohne --scanners vuln erzeugt nur ein Paketinventar. Lösung: immer --scanners vuln --format cyclonedx kombinieren.
  • .trivyignore.yaml wird ignoriert: YAML-Ignore-Dateien werden nicht automatisch geladen. Lösung: --ignorefile .trivyignore.yaml immer explizit übergeben.
  • Java-CVEs fehlen komplett: Die Java-DB (trivy-java-db) ist nicht in der Standard-DB enthalten. Lösung: trivy image --download-java-db-only vorab ausführen und die Java-DB ebenfalls cachen.
  • Image-Tag „latest" führt zu inkonsistenten Ergebnissen: Mutable Tags können sich zwischen Scan und Deployment ändern. Lösung: Images mit SHA256-Digest pinnen: myapp@sha256:abc123... sowohl im Scan als auch im Deployment.
  • SBOM nicht revisionssicher archiviert: Einmalige SBOM-Erzeugung reicht für NIS2/DSGVO nicht. Lösung: SBOM mit Datum und Image-Digest im Dateinamen speichern und in unveränderlichem Speicher (S3 Object Lock, Artefakt-Registry mit Retention-Policy) aufbewahren.
  • trivy-action auf @master gepinnt: Tags können kompromittiert werden (Supply-Chain-Angriff März 2026). Lösung: immer auf spezifischen Commit-SHA pinnen und Integrität per Cosign/SLSA-Attestation prüfen.

Häufige Fragen

Welche Severity soll ich für den CI/CD-Build-Break konfigurieren?

KMU-Empfehlung: --severity HIGH,CRITICAL --exit-code 1 bricht den Build ab. MEDIUM und LOW nur in Reports ohne Exit-Code 1 erfassen – sie erzeugen sonst zu viel Rauschen. Zusätzlich --ignore-unfixed setzen, damit nur Schwachstellen mit verfügbarem Patch den Build stoppen. Das reduziert die Zahl handlungsrelevanter Findings auf ein KMU-verträgliches Maß.

Wie unterscheidet sich Trivy von Grype und Docker Scout?

Trivy ist das breiteste Tool: IaC, Kubernetes, Secrets, SBOM, Misconfigs – alles in einer Binärdatei, komplett kostenlos und offline-fähig. Grype ist 30–40 % schneller und liefert EPSS/KEV-Prioritätsdaten nativ (ab v6), aber ohne IaC- oder Secret-Scan. Docker Scout ist tief in Docker Desktop integriert, für Teams aber kostenpflichtig (~5–9 USD/Monat/Nutzer). Für KMU ohne bestehendes Docker-Hub-Abo ist Trivy die erste Wahl.

Wie erzeuge ich ein SBOM für NIS2/DSGVO-Nachweise?

trivy image --scanners vuln --format cyclonedx --output sbom-$(date +%Y%m%d).json myapp:latest erzeugt ein CycloneDX-JSON mit Paketinventar und CVE-Verlinkungen. Das SBOM mit Image-Digest und Datum im Dateinamen archivieren. Alternativ SPDX-JSON mit --format spdx-json für breitere Toolchain-Kompatibilität. Entscheidend für Compliance ist die revisionssichere Aufbewahrung mit nachvollziehbarem Zeitstempel.

Was ist .trivyignore.yaml und wann brauche ich es statt .trivyignore?

Die YAML-Variante erlaubt strukturierte Dokumentation mit statement (Begründung), expired_at (Ablaufdatum) und paths/purls (betroffene Pakete). Sie ist Pflicht für Audit-Nachweise, da jede Risikoakzeptanz schriftlich begründet und zeitlich begrenzt sein muss. Die Plain-Text-.trivyignore eignet sich nur für schnelle Ad-hoc-Ausnahmen ohne Dokumentationsbedarf – und wird automatisch geladen, ohne --ignorefile.

Funktioniert Trivy ohne Internetverbindung (Air-Gapped)?

Ja: DB einmalig mit trivy image --download-db-only herunterladen (Default-Cache: ~/.cache/trivy), dann alle Folge-Scans mit --skip-db-update ausführen. Alternativ einen internen OCI-Registry-Mirror befüllen und --db-repository intern.example.com/trivy/trivy-db:2 verwenden. Die Java-DB muss separat mit --download-java-db-only gecacht werden.

Wie reduziere ich CVE-Findings durch bessere Base-Images?

Distroless-Images (Google) und Alpine-basierte Images reduzieren die CVE-Anzahl drastisch: Distroless hat keine Shell, keinen Paketmanager und minimale OS-Pakete. Alpine nutzt musl statt glibc und apk statt apt. Beide Ansätze verkleinern die Angriffsfläche so stark, dass schon der erste Scan nach dem Wechsel deutlich weniger Findings zeigt. Tags mit spezifischem Digest pinnen, damit der gescannte Stand dem deployten entspricht.

Fazit

Trivy ist für KMU die beste Wahl unter den Open-Source-Container-Scannern: kostenlos, offline-fähig, breiter Funktionsumfang. Der entscheidende Unterschied zum einmaligen trivy image nginx-Test liegt in der konsequenten Integration: Severity-Filter auf HIGH/CRITICAL reduzieren den Lärm auf handhabbare Mengen, .trivyignore.yaml mit Begründung und Ablaufdatum macht Risikoakzeptanz audit-tauglich, und tägliche Cron-Rescans laufender Container schließen die Lücke zwischen Build-Zeit und Produktion. SBOM-Export mit CycloneDX oder SPDX liefert den Compliance-Nachweis für NIS2 und DSGVO – vorausgesetzt, du archivierst die Dateien revisionssicher mit Datum und Image-Digest. Wer das alles kombiniert, hat eine KMU-taugliche Container-Security-Grundlage ohne Lizenzkosten.

Trivy fügt sich gut in bestehende Infrastruktur ein: Als nächste Schritte empfehlen sich die Absicherung der Docker-Hosts selbst (siehe Linux-Server absichern mit UFW und Fail2ban) sowie die Integration von Container-Metriken in ein Monitoring-Dashboard (siehe cAdvisor und Prometheus für Docker-Container-Metriken).

Weiterführende Anleitungen und Quellen

Quellen: Trivy Dokumentation – Filtering (trivy.dev) · Trivy Dokumentation – SBOM (trivy.dev) · Trivy Dokumentation – Container Image (trivy.dev) · GitHub – aquasecurity/trivy · Trivy DB Rate-Limit – GitHub Discussion #7699