Docker · hadolint · trivy · Container Testing · CI/CD
Dockerfile und Compose linten und testen
hadolint, trivy und Container Structure Tests

Dockerfiles, die nie statisch geprüft werden, akkumulieren stille Fehler: veraltete Basis-Images mit kritischen CVEs, ungültige Compose-Konfigurationen und strukturelle Probleme, die erst im Produktionsbetrieb sichtbar werden. Automatisches Linten und Testen schließt diese Lücke direkt in der CI-Pipeline.

16 Min. Lesezeit hadolint · trivy · compose validate · Container Structure Test · GitLab CI Docker 24+ · Docker Compose v2 · Linux

1. Warum Dockerfile-Linting mehr ist als Stilprüfung

Dockerfile-Linting wird häufig als reine Formatierungsprüfung missverstanden. Tatsächlich deckt ein vollständiger Dockerfile-Linting-Prozess drei Kategorien von Problemen auf: strukturelle Fehler (falsche Reihenfolge, ungültige Syntax), Security-Probleme (Root-User, Secrets in ENV, ungepinnte Basis-Images) und Performance-Schwachstellen (unnötige Layer, fehlende --no-install-recommends). Ein Dockerfile, das den Linter ohne Warnungen passiert, hat eine wesentlich höhere Baseline-Qualität als eines, das nie geprüft wurde.

Das Argument, man könne Qualität über Code-Reviews sicherstellen, greift bei Dockerfiles nur eingeschränkt. Die meisten Entwickler, die PHP-, Python- oder JavaScript-Code reviewen, sind keine Docker-Experten. Sie sehen nicht sofort, dass ein RUN apt-get install ohne --no-install-recommends hunderte Megabyte unnötige Pakete installiert, oder dass eine bestimmte Anordnung von COPY- und RUN-Befehlen den Layer-Cache bei jeder Code-Änderung invalidiert. Dockerfile linten gibt jedem Reviewer diese Expertise automatisch mit.

Die Integration von Dockerfile-Linting in CI-Pipelines ist dabei kein Aufwand, sondern eine Investition: Ein hadolint-Run dauert unter einer Sekunde, ein trivy-Scan eines fertigen Images unter einer Minute. Im Gegenzug werden kritische CVEs gefunden, bevor sie in der Produktion ausgenutzt werden, und Build-Probleme werden beim Entwickler statt beim Ops-Team sichtbar.

2. hadolint: Dockerfile-Linting mit Shellcheck-Integration

hadolint (Haskell Dockerfile Linter) ist der De-facto-Standard für das Dockerfile linten. Er prüft nicht nur Docker-spezifische Regeln, sondern integriert ShellCheck für alle Shell-Befehle in RUN-Anweisungen. Das bedeutet: ein Bash-Fehler in einem langen RUN-Block wird ebenso erkannt wie eine falsch geordnete COPY-Anweisung. hadolint gibt nummerierte Warnungen zurück (z.B. DL3008 für ungepinnte apt-Pakete, SC2086 für unquotierte Shell-Variablen), die direkt mit der offiziellen Dokumentation verlinkt sind.

hadolint ist als einzelnes Binary, Docker Image und GitHub Action verfügbar. Die Konfiguration erfolgt über eine .hadolint.yaml-Datei im Projektroot, in der einzelne Regeln ignoriert oder auf "info"-Level heruntergestuft werden können. Das erlaubt Teams, einen pragmatischen Einstieg zu finden: zunächst alle kritischen Warnungen (Error-Level) beheben, dann schrittweise weitere Regeln aktivieren. Das Dockerfile linten mit hadolint ist der erste Schritt in jeder Docker-CI-Pipeline.


# Install hadolint as a single binary (Linux amd64)
curl -L -o /usr/local/bin/hadolint \
  https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
chmod +x /usr/local/bin/hadolint

# Basic lint — exit code 1 if errors found
hadolint Dockerfile

# JSON output for CI artifact processing
hadolint --format json Dockerfile > hadolint-report.json

# Configure rule overrides in .hadolint.yaml
cat > .hadolint.yaml << 'EOF'
failure-threshold: error      # only fail on errors, not warnings
ignore:
  - DL3008                    # allow unpinned apt packages (team decision)
  - DL3018                    # allow unpinned apk packages
trustedRegistries:
  - registry.mironsoft.de     # suppress DL3002 for internal registry
EOF

# Run with config — warns but does not fail for ignored rules
hadolint --config .hadolint.yaml Dockerfile

# Scan all Dockerfiles in project
find . -name "Dockerfile*" -not -path "./.git/*" \
  -exec hadolint --config .hadolint.yaml {} \;

3. trivy: Vulnerability-Scanning für Images und Dockerfiles

trivy von Aqua Security ist das meistgenutzte Open-Source-Tool für Container-Vulnerability-Scans. Es prüft fertige Images gegen bekannte CVE-Datenbanken (NVD, Alpine SecDB, Debian Security, RedHat, GitHub Advisories) und findet Schwachstellen in OS-Paketen, Python-Paketen, npm-Modulen, Composer-Paketen und Java-Abhängigkeiten. Ein trivy-Scan liefert CVE-ID, Schweregrad (CRITICAL, HIGH, MEDIUM, LOW), betroffene Version und verfügbare Fix-Version – alles, was für eine fundierte Risikobewertung nötig ist.

Neben fertigen Images kann trivy auch Dockerfiles direkt auf Misconfigurationen prüfen (trivy config Dockerfile) und deckt dabei ähnliche Probleme wie hadolint auf, aber mit einem zusätzlichen Fokus auf Security-Policies. Der --exit-code 1-Flag lässt den Scan mit einem Fehler-Exit-Code fehlschlagen, wenn CRITICAL- oder HIGH-Schwachstellen gefunden werden – ideal für CI-Pipelines, die einen Build bei kritischen Sicherheitsproblemen stoppen sollen. Mit --ignore-unfixed werden Schwachstellen ohne verfügbaren Fix aus dem Bericht gefiltert, was die Reparierbarkeits-Fokussierung verbessert.

4. docker compose config: Compose-Dateien validieren

Docker Compose Dateien wachsen in komplexen Projekten schnell zu mehreren hundert Zeilen YAML – mit Overrides, Fragments und Environment-Substitutions. Ungültige YAML-Syntax, falsch referenzierte Services oder fehlende required-Felder erzeugen Fehler erst beim docker compose up-Aufruf. docker compose config validiert und normalisiert die Compose-Konfiguration, löst alle Overrides und Variablen auf und gibt das vollständige, zusammengeführte Ergebnis aus. Wenn die Konfiguration ungültig ist, schlägt der Befehl mit einer klaren Fehlermeldung fehl.

Das Compose linten mit docker compose config --quiet ist der schnellste Weg, syntaktische und strukturelle Fehler in Compose-Dateien zu finden, ohne einen Container zu starten. In CI-Pipelines kann docker compose config --quiet && echo "Config valid" als Vorstufe zum eigentlichen Build verwendet werden. Für Teams, die mit mehreren Compose-Dateien (Base, Override, Dev, Prod) arbeiten, ist dieser Schritt besonders wertvoll – er stellt sicher, dass die kombinierte Konfiguration wirklich valide ist, bevor sie deployed wird.


# Validate and print merged compose config (resolves overrides and env vars)
docker compose -f compose.yaml -f compose.prod.yaml config

# Silent validation — exit code only (useful in CI)
docker compose config --quiet && echo "Compose config is valid"

# trivy: scan a built image for vulnerabilities
trivy image --exit-code 1 --severity CRITICAL,HIGH \
  --ignore-unfixed registry.mironsoft.de/myapp:latest

# trivy: scan Dockerfile for misconfigurations
trivy config --exit-code 1 Dockerfile

# trivy: generate SARIF report for GitHub/GitLab security dashboards
trivy image --format sarif --output trivy-results.sarif \
  registry.mironsoft.de/myapp:latest

# Container Structure Tests — verify image contents
# Install: go install github.com/GoogleContainerTools/container-structure-test@latest
container-structure-test test \
  --image registry.mironsoft.de/myapp:latest \
  --config structure-test.yaml

# Example structure-test.yaml
cat > structure-test.yaml << 'EOF'
schemaVersion: "2.0.0"
commandTests:
  - name: "PHP version check"
    command: "php"
    args: ["--version"]
    expectedOutput: ["PHP 8.4"]
fileExistenceTests:
  - name: "Entrypoint script exists"
    path: "/usr/local/bin/docker-entrypoint.sh"
    shouldExist: true
    permissions: "-rwxr-xr-x"
metadataTests:
  - user: "www-data"
    exposedPorts: ["9000"]
EOF

5. Container Structure Tests: Inhalte und Verhalten prüfen

Container Structure Tests (Google) gehen einen Schritt über statisches Linting hinaus: Sie starten das fertige Image und prüfen dessen Inhalt und Verhalten. Das umfasst vier Testtypen: Command-Tests (Befehle ausführen und Ausgabe prüfen), File-Existence-Tests (Dateien vorhanden und mit korrekten Berechtigungen), File-Content-Tests (Dateiinhalte mit Regex prüfen) und Metadata-Tests (USER, EXPOSE, ENV, LABEL). Das Ergebnis ist ein automatisierter Qualitätsvertrag für jedes Image: wenn das Image gebaut wird und alle Structure-Tests bestehen, ist bekannt, dass es die definierten Anforderungen erfüllt.

Container Structure Tests sind besonders wertvoll für Images, die an Teams oder externe Nutzer weitergegeben werden. Der Test-YAML dient gleichzeitig als maschinenlesbarer und menschenlesbarer Vertrag: "Dieses Image enthält PHP 8.4, den www-data-User, den Entrypoint unter /usr/local/bin und lauscht auf Port 9000." Änderungen am Dockerfile, die diesen Vertrag verletzen – z.B. versehentliches Upgrade auf PHP 8.5 oder eine geänderte Dateistruktur – werden sofort beim nächsten CI-Run erkannt, ohne dass jemand das Image manuell inspizieren muss.

6. dockle: CIS-Benchmark und Security-Best-Practices

dockle von Goodwith Technology ergänzt trivy und hadolint mit einem Fokus auf Security-Best-Practices nach CIS Docker Benchmark. Es prüft fertige Images auf Kriterien, die in keinem der anderen Tools vollständig abgedeckt sind: ob der Container als Root läuft, ob setuid-Binaries vorhanden sind, ob Healthcheck-Konfigurationen fehlen, ob Content-Trust aktiviert ist und ob Secrets in Labels oder Environment-Variablen stecken. Die Warnungen folgen einem Kategoriesystem von FATAL bis INFO.

In der Praxis komplettiert dockle die Linting-Pipeline: hadolint prüft das Dockerfile-Quellcode, trivy prüft CVEs in Paketen, dockle prüft die Security-Konfiguration des fertigen Images. Kein einzelnes Tool deckt alle drei Kategorien ab. Die Kombination der drei gibt einen ganzheitlichen Blick auf die Qualität und Sicherheit eines Docker-Setups. dockle ist als Binary und Docker Image verfügbar und produziert JSON-Ausgabe, die sich in CI-Dashboards integrieren lässt.

7. Vollständige CI-Pipeline: alle Tools kombiniert

Eine vollständige Docker-Linting-und-Test-Pipeline in GitLab CI läuft in drei Phasen: Lint (hadolint + compose config), Build (docker build mit BuildKit), Test (trivy + dockle + Container Structure Tests). Die Lint-Phase ist ohne Images schnell und schlägt Fehler-Commits frühzeitig zurück. Die Test-Phase läuft nach dem Build und prüft das fertige Artefakt. Kritische CVEs und Security-Probleme können den Pipeline-Status auf "failed" setzen, während niedrige Schweregrade nur Warnungen erzeugen.

Das Caching ist bei dieser Pipeline-Struktur kritisch: Docker-Layer-Caching über DOCKER_BUILDKIT=1 und Registry-Caching (--cache-from) stellt sicher, dass auch bei vollständigen Scans die Build-Zeit akzeptabel bleibt. trivy-Datenbanken können als CI-Artifact gecacht werden, um wiederholte Downloads zu vermeiden. Eine solche Dockerfile-Linting-Pipeline gibt Teams die Sicherheit, dass jeder Merge-Request ein geprüftes, getestetes Image erzeugt.


# .gitlab-ci.yml — complete Docker lint and test pipeline
stages:
  - lint
  - build
  - test

variables:
  DOCKER_BUILDKIT: "1"
  IMAGE_TAG: "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"

dockerfile-lint:
  stage: lint
  image: hadolint/hadolint:latest-alpine
  script:
    - hadolint --config .hadolint.yaml Dockerfile
    - hadolint --config .hadolint.yaml docker/php/Dockerfile
  rules:
    - changes: ["Dockerfile", "docker/**/*"]

compose-validate:
  stage: lint
  image: docker:24-cli
  script:
    - docker compose -f compose.yaml -f compose.prod.yaml config --quiet
  rules:
    - changes: ["compose*.yaml", "docker-compose*.yml"]

build-image:
  stage: build
  image: docker:24
  services: [docker:24-dind]
  script:
    - docker build --cache-from "$CI_REGISTRY_IMAGE:latest"
        --tag "$IMAGE_TAG" .
    - docker push "$IMAGE_TAG"

trivy-scan:
  stage: test
  image: aquasec/trivy:latest
  script:
    - trivy image --exit-code 1 --severity CRITICAL,HIGH
        --ignore-unfixed --format sarif
        --output trivy.sarif "$IMAGE_TAG"
  artifacts:
    reports:
      sast: trivy.sarif
    expire_in: 7 days

structure-test:
  stage: test
  image: gcr.io/gcp-runtimes/container-structure-test:latest
  script:
    - container-structure-test test
        --image "$IMAGE_TAG"
        --config tests/structure-test.yaml

8. Bestehende Images: Baseline-Scans und Ausnahmeregeln

Bei der Einführung von Dockerfile-Linting in bestehende Projekte ist das erste trivy-Scan-Ergebnis oft ernüchternd: Dutzende von CVEs, viele davon in transitiven Abhängigkeiten ohne sofort verfügbaren Fix. Das "alles reparieren oder nichts aktivieren"-Dilemma lässt sich mit trivy's .trivyignore-Datei auflösen: bekannte, akzeptierte Schwachstellen können mit CVE-ID, Ablaufdatum und Begründung ausgenommen werden. Das verhindert, dass legitime Ausnahmen die CI-Pipeline dauerhaft rot machen, während neue kritische Schwachstellen weiterhin sofort auffallen.

Für hadolint funktioniert derselbe Ansatz über .hadolint.yaml mit dem ignore-Schlüssel. Teams, die in einem bestehenden Projekt mit hundert Warnungen starten, können schrittweise vorgehen: zunächst nur FATAL/ERROR-Level aktivieren, dann wöchentlich weitere Regeln hinzufügen, bis die vollständige Baseline erreicht ist. Dieses iterative Vorgehen ist pragmatischer als der Versuch, alle Probleme gleichzeitig zu lösen, und führt in der Praxis zu höherer Akzeptanz im Team.

9. Linting-Tools im direkten Vergleich

Die vier Tools – hadolint, trivy, dockle und Container Structure Tests – haben unterschiedliche Stärken und Anwendungsbereiche. Sie ersetzen einander nicht, sondern ergänzen sich. Ein vollständiger Docker-Linting-Stack nutzt alle vier in der richtigen Reihenfolge.

Tool Was wird geprüft Input Besonderheit
hadolint Dockerfile-Struktur, Shell-Syntax Dockerfile (Quellcode) ShellCheck integriert, sehr schnell (<1s)
trivy CVEs in OS- und App-Paketen Fertiges Image oder Filesystem SARIF-Output, breiteste CVE-Datenbank
dockle CIS-Benchmark, Security-Config Fertiges Image User, setuid, Health, Secrets in Labels
Container Structure Test Dateien, Befehle, Metadata Laufendes Image YAML-Testkontrakte, flexibel erweiterbar
docker compose config Compose-Syntax und Vollständigkeit compose.yaml Auflösen von Overrides und Variablen

Die sinnvolle Reihenfolge in der CI-Pipeline: hadolint und compose-Validierung zuerst (schnell, kein Image nötig), dann Build, dann trivy und dockle (brauchen das fertige Image), zuletzt Container Structure Tests (brauchen ein laufendes Image). Wer alle fünf Checks implementiert hat, hat eine robuste Dockerfile-Linting-Pipeline, die strukturelle, sicherheitsrelevante und funktionale Qualität gleichzeitig sicherstellt.

Mironsoft

Docker CI/CD, Container Security und DevOps-Automatisierung

Dockerfile-Linting in eure CI-Pipeline integrieren?

Wir richten hadolint, trivy, dockle und Container Structure Tests in eurer GitLab- oder GitHub-Pipeline ein und bauen eine vollständige Docker-Qualitätssicherung auf, die bei jedem Commit automatisch prüft.

Pipeline-Setup

hadolint, trivy und Structure Tests in GitLab CI oder GitHub Actions einrichten

Baseline-Analyse

Bestehende Images und Dockerfiles scannen, Baseline erstellen, Prioritäten setzen

Schulung

Team-Workshop zu Docker-Best-Practices und Interpretation der Scan-Ergebnisse

10. Zusammenfassung

Automatisches Dockerfile linten und testen ist kein Zusatz, sondern ein fundamentaler Bestandteil einer produktionsreifen Container-Infrastruktur. hadolint prüft Dockerfiles statisch auf strukturelle und Shell-Fehler. trivy findet CVEs in fertigen Images und Infrastruktur-Code. dockle validiert die Security-Konfiguration gegen CIS-Benchmarks. Container Structure Tests verifizieren Inhalt und Verhalten von Images als automatisierte Kontrakte. docker compose config validiert Compose-Konfigurationen vor dem Deployment.

Die Einführung dieser Tools muss nicht auf einmal erfolgen. Ein pragmatischer Start: hadolint mit failure-threshold: error in der CI aktivieren, parallel eine trivy-Baseline für bestehende Images erstellen und die kritischsten CVEs beheben. Innerhalb von zwei Sprints hat ein Team damit eine funktionierende Dockerfile-Linting-Pipeline, die bei jedem Merge-Request automatisch prüft und das Qualitätsniveau der Container-Infrastruktur kontinuierlich verbessert.

Dockerfile linten und testen — Das Wichtigste auf einen Blick

Statisches Linting

hadolint für Dockerfiles (inkl. ShellCheck). docker compose config --quiet für Compose-Dateien. Beide laufen ohne gebautes Image in Sekunden.

Vulnerability-Scanning

trivy image mit --exit-code 1 --severity CRITICAL,HIGH. SARIF-Output für Security-Dashboards. .trivyignore für bekannte Ausnahmen.

Struktur & Security

Container Structure Tests als maschinenlesbarer Image-Vertrag. dockle für CIS-Benchmark. Beide laufen nach dem Build gegen das fertige Image.

Einführungsstrategie

Schrittweise: zuerst Error-Level, dann schrittweise alle Regeln. Baseline-Scan für bestehende Images. Ausnahmen mit Ablaufdatum und Begründung dokumentieren.

11. FAQ: Dockerfile und Compose linten und testen

1Was ist hadolint und warum ist es der Standard?
Haskell-basierter Dockerfile-Linter mit integriertem ShellCheck. Läuft unter einer Sekunde, hat umfangreiche Regelbasis und konfigurierbare Ausnahmen per .hadolint.yaml.
2Unterschied trivy vs. dockle?
trivy: CVE-Datenbank-Scans für Pakete und App-Dependencies. dockle: CIS-Benchmark-Prüfung (User, setuid, Healthcheck, Secrets in Env). Beide ergänzen sich, keines ersetzt das andere.
3Was sind Container Structure Tests?
Google-Tool, das fertige Images auf Dateiinhalte, Befehlsausgaben und Metadata prüft. YAML-Testdatei dient als automatisierbarer Image-Vertrag für Teams und CI-Pipelines.
4Wie validiere ich docker-compose.yaml automatisch?
docker compose config --quiet prüft Syntax und Vollständigkeit, löst Overrides auf, Exit-Code 1 bei Fehlern. Läuft in Sekunden ohne gebautes Image – ideal als erster CI-Schritt.
5Umgang mit vielen trivy-Warnungen?
.trivyignore für bekannte CVEs mit Begründung und Ablaufdatum. --ignore-unfixed filtert nicht reparierbare Schwachstellen. Schrittweise: CRITICAL zuerst, dann HIGH.
6hadolint in GitHub Actions / GitLab CI?
GitHub: hadolint/hadolint-action. GitLab: image: hadolint/hadolint:latest-alpine, Script: hadolint Dockerfile. JSON-Output für Dashboards. Beides läuft out-of-the-box.
7Was ist SARIF?
Static Analysis Results Interchange Format – standardisiertes JSON für Scan-Ergebnisse. GitHub und GitLab visualisieren SARIF-Reports direkt in ihren Security-Dashboards. trivy --format sarif erzeugt kompatible Ausgabe.
8Wann Structure Tests statt manueller Inspektion?
Immer bei regelmäßig gebauten Images und definierten Qualitätsanforderungen. YAML-Test dient als Dokumentation und automatischer Test gleichzeitig. Teamarbeit und CI machen Automatisierung unverzichtbar.
9Overhead der Linting-Pipeline?
hadolint: <1s. compose config: <1s. trivy: 30–90s (mit Cache schneller). dockle: 10–30s. Structure Tests: <60s. Gesamt unter 3 Minuten für eine vollständige Prüfung.
10Welche hadolint-Regeln sind am wichtigsten?
DL3007 (latest-Tag), DL3008/DL3018 (ungepinnte Pakete), DL3009 (apt-get update allein), DL3025 (Shell-Form bei CMD/ENTRYPOINT), DL4006 (kein pipefail), SC2xxx (ShellCheck-Regeln).