Zum Inhalt springen

Dockerfile Linter · Docker

Ein Dockerfile linten, bevor es ins Review geht.

Siebzehn Regeln über einen echten Parse der Datei: das apt-get update, das einen veralteten Index ausliefern wird, das Secret in Ihrer Image-History, die Exec-Form, die Docker leise als Shell-String ausgeführt hat. Jeder Fund nennt eine Zeile und trägt den Fix — und nichts, was Sie einfügen, verlässt den Tab.

Läuft in Ihrem Browser — nichts, was Sie einfügen, verlässt diese Seite. Wie wir das belegen

Läuft in Ihrem Browser 17 Regeln, DF001–DF017 Keine Anmeldung Aktualisiert am 31.07.2026

Dockerfile Linter Playground

Examples

A build stage is one FROM and every instruction under it; a multi-stage file has several, and only the last one ships. The layer cache reuses an instruction's result until that instruction — or anything it copied — changes.

Dockerfile input

Results update as you type — press Enter to run now.

Press Esc to release keyboard focus from the editor; /Ctrl + Enter lints and leaves the editor. Tap a Line badge to jump to that line. Nothing you paste is uploaded.

Findings

Paste a Dockerfile above — or tap an example — to see line-numbered findings here, each with the reason it matters and the fix.

Die Lücke

Ein Dockerfile, das baut, ist kein Dockerfile, das stimmt.

Jeder Fehler im Katalog unten baut sauber. FROM node baut. apt-get update in einem eigenen Layer baut — und installiert dann aus einem Index, der seit letztem Quartal im Cache liegt. CMD ['nginx'] baut, und Docker führt leise /bin/sh -c "['nginx']" aus statt des Prozesses, den Sie als PID 1 gemeint haben. Ein grüner Build ist kein Beweis; er ist das Fehlen eines Beweises.

Lassen Sie das Dockerfile von einem Assistenten schreiben, erben Sie dieselbe Fehlerklasse mit mehr Selbstsicherheit obendrauf. Generierte Dockerfiles sind flüssig und plausibel: Sie kommen durchs Review, weil sie wie jedes andere Dockerfile aussehen, und tragen Defekte in der Art von DF007 und DF014, auf die kein fehlgeschlagener Build je zeigen wird. Die Behauptung zu prüfen dauert Sekunden; das Schwere ist zu wissen, was man prüfen muss.

hadolint ist die richtige Antwort in der CI, und dies hier ersetzt es nicht. Dies ist der Fall, den es nicht abdeckt: eine Datei, die Sie jetzt geprüft haben wollen, aus dem Browser, auf einer Maschine, auf der Sie keine Binary installieren dürfen, ohne ein Dockerfile voller interner Registry-Hosts und Build-Argumente auf den Server von jemand anderem zu schicken. Siebzehn Regeln, ein Fix zu jedem Fund, und eine ausdrückliche Liste dessen, was still bleibt — denn ein Linter, den man nicht prüfen kann, ist ein Linter, dem man glauben muss.

Läuft schon ein Container? Docker Run zu Compose macht aus dem Kommando eine Service-Datei, und Env Example Checker findet die Variablen, die Ihr Image erwartet und Ihre Umgebung nie setzt.

Die Pipeline

So funktioniert es.

Vier Schritte, alle in Ihrem Browser-Tab, neu ausgeführt während Sie tippen.

  1. Parsen wie BuildKit.

    Zeilenfortsetzungen, ganze Kommentarzeilen darin, Heredoc-Rümpfe, die JSON-Exec-Form, Stages und die ARG-Tabelle vor dem ersten FROM. Eine `# escape=`-Direktive dreht die Fortsetzungszeichen wirklich um.

  2. Den Parse analysieren, nicht den Text.

    Siebzehn Regeln lesen Instruktionen und Stages. Die Shell-Regeln laufen über einen quote-bewussten Scan jedes RUN: eine Pipe in einer Zeichenkette ist keine Pipe, und ein Heredoc-Rumpf ist trotzdem Shell.

  3. Zeile und Fix melden.

    Jeder Fund nennt die PHYSISCHE Zeile — Zeile 4 eines gefalteten RUN, nicht die Zeile, in der das RUN begann — und trägt die Änderung, die zu machen ist, fertig zum Einfügen in ein Review.

  4. Sagen, was nicht geprüft wurde.

    Die Regeln, die er bewusst nicht ausführt, stehen mit Begründung auf dieser Seite. Ein Linter, den man nicht prüfen kann, ist ein Linter, dem man glauben muss.

Referenz

Der Regelkatalog.

Alle siebzehn Regeln: drei Fehler, zwölf Warnungen und zwei Hinweise. Ein Fehler heißt, Docker lehnt die Datei ab oder führt leise etwas anderes aus; eine Warnung heißt, es baut und ist trotzdem falsch; ein Hinweis ist wissenswert. Jeder Fund im Playground verlinkt auf seine Regel hier.

Regel Schwere Was sie findet
DF001 Fehler Die erste Instruktion muss FROM sein
DF002 Warnung Das Basis-Image auf einen Tag pinnen
DF003 Fehler COPY --from muss eine frühere Stage nennen
DF004 Warnung Für lokale Dateien COPY nehmen, nicht ADD
DF005 Warnung Ein Remote-ADD verifizieren
DF006 Warnung WORKDIR sollte absolut sein
DF007 Warnung Update und Installation im selben RUN
DF008 Warnung Den Paket-Cache im selben Layer aufräumen
DF009 Warnung Keine Secrets in ENV oder ARG backen
DF010 Warnung Die letzte Stage nicht als root laufen lassen
DF011 Warnung WORKDIR statt cd verwenden
DF012 Warnung Keinen Download in eine Shell pipen
DF013 Warnung Ein CMD und ein ENTRYPOINT pro Stage
DF014 Fehler Die Exec-Form muss gültiges JSON sein
DF015 Warnung sudo aus Build-Schritten entfernen
DF016 Hinweis MAINTAINER ist veraltet
DF017 Hinweis Das Manifest vor den Dependencies kopieren
DF001 Fehler

Die erste Instruktion muss FROM sein

Nur ARG (und Kommentare) dürfen vor FROM stehen. Alles andere hat kein Image, auf dem es arbeiten könnte, und Docker verweigert den Build. Eine Datei ohne jedes FROM fällt unter dieselbe Regel.

Statt

DOCKERFILE
RUN apt-get update
FROM debian:bookworm-slim

So schreiben

DOCKERFILE
FROM debian:bookworm-slim
RUN apt-get update
DF002 Warnung

Das Basis-Image auf einen Tag pinnen

Eine Referenz ohne Tag bedeutet :latest, und :latest wandert. Digest-Pins, scratch, Verweise auf eine frühere Stage und Tags, die aus einem nicht auflösbaren Build-Argument kommen, bleiben unangetastet.

Statt

DOCKERFILE
FROM node
FROM node:latest

So schreiben

DOCKERFILE
FROM node:22-bookworm-slim
DF003 Fehler

COPY --from muss eine frühere Stage nennen

Eine Stage kann nur aus einer darüber definierten kopieren. Ein numerischer Index auf oder hinter der aktuellen Stage und ein blanker Name, der auf keine Stage passt, brechen den Build beide mit „invalid from flag value“. Alles, was Registry, Tag oder Digest trägt, gilt als externes Image und bleibt unangetastet.

Statt

DOCKERFILE
FROM alpine:3.20
COPY --from=builder /out /srv

So schreiben

DOCKERFILE
FROM golang:1.23-alpine AS builder
FROM alpine:3.20
COPY --from=builder /out /srv
DF004 Warnung

Für lokale Dateien COPY nehmen, nicht ADD

ADD entpackt lokale Tar-Archive automatisch und kann URLs holen — damit verhält sich ein einfaches Kopieren anders, als die Zeile sagt. Ein Archiv zu entpacken ist die eine Aufgabe, für die ADD sich lohnt, deshalb bleibt ein ADD still, dessen Quellen alle Tarballs sind.

Statt

DOCKERFILE
ADD entrypoint.sh /entrypoint.sh

So schreiben

DOCKERFILE
COPY entrypoint.sh /entrypoint.sh
DF005 Warnung

Ein Remote-ADD verifizieren

Ein Remote-ADD backt in ein Layer, was der Server gerade zurückgibt, und im Build merkt nichts, wenn sich das ändert. BuildKits --checksum= erfüllt die Regel; Herunterladen und Verifizieren in einem RUN ebenfalls.

Statt

DOCKERFILE
ADD https://example.com/tool.tgz /tmp/tool.tgz

So schreiben

DOCKERFILE
RUN curl -fsSL https://example.com/tool.tgz -o /tmp/tool.tgz \
 && echo "<sha256sum>  /tmp/tool.tgz" | sha256sum -c -
DF006 Warnung

WORKDIR sollte absolut sein

Ein relatives WORKDIR wird gegen das vorherige WORKDIR aufgelöst — welches Verzeichnis es also wählt, hängt von den Zeilen darüber ab und ändert sich, sobald sie umsortiert werden. Pfade, die mit einer Variablen beginnen, bleiben unangetastet.

Statt

DOCKERFILE
WORKDIR app

So schreiben

DOCKERFILE
WORKDIR /app
DF007 Warnung

Update und Installation im selben RUN

Jedes RUN ist ein eigenes Layer. Ein gecachtes Update-Layer plus ein neu gebautes Install-Layer heißt: Installation aus einem Paket-Index, der Monate alt sein kann. Deckt apt-get, apt und apk ab.

Statt

DOCKERFILE
RUN apt-get update
RUN apt-get install -y curl

So schreiben

DOCKERFILE
RUN apt-get update \
 && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*
DF008 Warnung

Den Paket-Cache im selben Layer aufräumen

Der Paket-Index wird mit dem Layer committet, ihn in einem späteren RUN zu löschen holt also nichts zurück. Erfüllt durch rm -rf /var/lib/apt/lists/* bei apt, --no-cache bei apk und dnf/yum clean all — und vollständig unterdrückt, wenn das RUN ein --mount=type=cache trägt, denn dann landet der Cache nie im Image.

Statt

DOCKERFILE
RUN apt-get update && apt-get install -y curl

So schreiben

DOCKERFILE
RUN apt-get update \
 && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*
DF009 Warnung

Keine Secrets in ENV oder ARG backen

Jeder ENV- und ARG-Wert liegt in der Image-History und ist mit docker history für jeden lesbar, der das Image ziehen kann. Ihn in einem späteren Layer zu überschreiben entfernt ihn nicht. Gemeldet werden nur Namen, die wie ein Credential aussehen UND einen nicht leeren Wert tragen.

Statt

DOCKERFILE
ARG NPM_TOKEN=npm_liveTokenValue
ENV DB_PASSWORD=hunter2

So schreiben

DOCKERFILE
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN="$(cat /run/secrets/npm_token)" npm ci
DF010 Warnung

Die letzte Stage nicht als root laufen lassen

Ohne USER läuft der Prozess als uid 0. Geprüft wird nur die LETZTE Stage — Build-Stages brauchen root legitim — und darin nur das LETZTE USER, denn das ist das, als das der Container läuft.

Statt

DOCKERFILE
FROM node:22-bookworm-slim
COPY . /app
CMD ["node", "/app/server.js"]

So schreiben

DOCKERFILE
FROM node:22-bookworm-slim
COPY --chown=node:node . /app
USER node
CMD ["node", "/app/server.js"]
DF011 Warnung

WORKDIR statt cd verwenden

Ein cd hält nur für das RUN, in dem es steht. Die nächste Instruktion startet wieder im WORKDIR der Stage — eine häufige Ursache für „no such file or directory“ beim folgenden COPY oder CMD.

Statt

DOCKERFILE
RUN cd /src && make

So schreiben

DOCKERFILE
WORKDIR /src
RUN make
DF012 Warnung

Keinen Download in eine Shell pipen

curl … | sh führt aus, was der Server zurückgibt — ohne Signatur, ohne Checksumme. Erkannt in gefalteten RUN-Zeilen und in Heredoc-Rümpfen, und gemeldet auf der Zeile, in der der Download steht.

Statt

DOCKERFILE
RUN curl -fsSL https://get.example.com/install.sh | sh

So schreiben

DOCKERFILE
RUN curl -fsSL https://get.example.com/install.sh -o /tmp/install.sh \
 && echo "<sha256sum>  /tmp/install.sh" | sha256sum -c - \
 && sh /tmp/install.sh
DF013 Warnung

Ein CMD und ein ENTRYPOINT pro Stage

Docker behält das letzte in einer Stage und verwirft den Rest stillschweigend. Pro Stage gezählt — eine Build-Stage mit eigenem CMD ist also in Ordnung, und ONBUILD-gewrappte Instruktionen zählen nicht mit.

Statt

DOCKERFILE
CMD ["node", "server.js"]
CMD ["node", "worker.js"]

So schreiben

DOCKERFILE
CMD ["node", "server.js"]
# der Worker läuft als eigener Container, nicht als zweites CMD
DF014 Fehler

Die Exec-Form muss gültiges JSON sein

Docker liest das Argument nur als Exec-Form-Array, wenn es als JSON parst — doppelte Anführungszeichen, kein Komma am Ende. Sonst führt es das Ganze stillschweigend als Shell-Form über /bin/sh -c aus, Klammern inklusive.

Statt

DOCKERFILE
CMD ['nginx', '-g', 'daemon off;']

So schreiben

DOCKERFILE
CMD ["nginx", "-g", "daemon off;"]
DF015 Warnung

sudo aus Build-Schritten entfernen

Ein Build-Schritt läuft schon als USER der Stage — root, solange Sie nichts geändert haben — ohne TTY, und die meisten Basis-Images liefern sudo gar nicht mit. Wechseln Sie den User stattdessen explizit.

Statt

DOCKERFILE
RUN sudo apt-get install -y curl

So schreiben

DOCKERFILE
USER root
RUN apt-get update \
 && apt-get install -y --no-install-recommends curl \
 && rm -rf /var/lib/apt/lists/*
USER app
DF016 Hinweis

MAINTAINER ist veraltet

Veraltet seit Docker 1.13, also seit 2017. Es baut weiter, aber nichts liest es — was Registries und Scanner anzeigen, ist das OCI-Label.

Statt

DOCKERFILE
MAINTAINER ops@example.com

So schreiben

DOCKERFILE
LABEL org.opencontainers.image.authors="ops@example.com"
DF017 Hinweis

Das Manifest vor den Dependencies kopieren

Ein COPY . früher in der Stage als die Dependency-Installation heißt: jede reine Quellcode-Änderung invalidiert das Install-Layer und installiert alles neu. Erkennt npm, yarn, pnpm, pip -r, Bundler, Composer, go mod download und cargo. Kombinieren Sie das Umsortieren mit einer .dockerignore, damit node_modules und .git gar nicht erst in den Kontext gelangen.

Statt

DOCKERFILE
COPY . /app
RUN npm ci

So schreiben

DOCKERFILE
COPY package.json package-lock.json ./
RUN npm ci
COPY . .

Die Grenze

Was er bewusst nicht meldet.

Jeder dieser Punkte wurde geprüft und verworfen, und dieselbe Liste steht als Kommentar am Anfang der Engine. Stille, die man nachlesen kann, ist mehr wert als eine Regel, die man zu ignorieren lernt.

Version-Pinning über den Tag hinaus

apt-get install curl ohne Version und pip install django ohne == sind beide legitim. DF002 verlangt einen Tag; einen Digest oder ein gepinntes Paketset verlangt er nie.

EXPOSE

Es veröffentlicht von sich aus nichts. Ein fehlendes bricht nichts und ein zusätzliches kostet nichts — reine Meinung.

HEALTHCHECK

Von Kubernetes komplett ignoriert, und dort lässt dieses Publikum seine Images laufen.

pip- und npm-Download-Caches

Real, aber klein, und die Flags wandern zwischen Tool-Versionen. DF008 deckt die System-Paketmanager ab, dort liegen die Megabytes.

update; install mit Semikolon verbunden

Das verdeckt ein fehlgeschlagenes Update, aber beide Kommandos liegen IM selben Layer, DF007 hat also nichts zu sagen. Eine eigene Regel über Shell-Operatoren wäre Lärm.

Tags, die aus einer nicht auflösbaren Variablen kommen

FROM node:$TAG wird zur Build-Zeit per --build-arg entschieden. Zu raten, worauf das auflöst, wäre eine selbstsicher falsche Antwort.

COPY --from auf ein Registry-Image

Alles mit Slash, Doppelpunkt oder @digest ist eine legale externe Referenz, also bleibt DF003 still.

Tiefe Shell-Semantik

Hier gibt es keinen Shell-AST: kein set -e-Schließen, keine Exit-Code-Analyse, kein Variablen-Tracking. Der Scanner ist quote-bewusst und hört dort auf. Er liest auch das argv eines Exec-Form-RUN nicht als Shell-Text.

Nicht terminierte Heredocs, unbalancierte Quotes und # syntax=-Dialekte

Docker meldet das selbst, und ein Teil-Parse darf darauf keine Regel erfinden.

Alles außerhalb der Datei

Es wird kein Image gezogen, keine Registry befragt und keine .dockerignore gelesen — nichts davon steht in dem Text, den Sie eingefügt haben.

Grenzen, genannt statt versteckt: Der Linter scannt bis zu 200.000 Zeichen, behält höchstens 20 Funde pro Regel und 200 insgesamt und nennt die echte Anzahl, sobald eine Grenze greift.

Nächster Schritt

Den Report ins Review einfügen.

„Copy report“ gibt Ihnen den ganzen Lauf als Klartext — eine Zeile pro Fund, mit Zeilennummer, Regel-ID und Fix. Und dann weiter: das docker run-Kommando in einen Compose-Service verwandeln oder prüfen, ob die Variablen, die das Image erwartet, wirklich existieren.

report.txt
Dockerfile Linter — 1 error, 12 warnings, 2 info across 15 lines

ERRORS (1)
  L14 DF014: CMD looks like a JSON array but is not valid JSON.

WARNINGS (12)
  L1 DF002: Base image "ubuntu" has no tag, so Docker resolves it to :latest.
  L6 DF007: apt-get update runs without an install in the same RUN.
  L8 DF012: Piping a download straight into a shell.

FAQ

Fragen, beantwortet.

Tippen Sie auf eine Frage, um die Antwort aufzuklappen.

Siebzehn Regeln, DF001 bis DF017, jede davon ein Fehler, der trotzdem baut: ein Basis-Image ohne Tag oder mit :latest, ein COPY --from auf eine Stage, die es noch nicht gibt, ADD dort, wo COPY hingehört, ein unverifizierter Remote-Download, ein relatives WORKDIR, ein apt-get update allein in seinem eigenen Layer, ein im Image zurückgelassener Paket-Cache, ein per ENV oder ARG in die Image-History geschriebenes Secret, eine letzte Stage, die als root läuft, cd statt WORKDIR, curl in eine Shell gepipet, ein zweites CMD, das stillschweigend gewinnt, eine JSON-Exec-Form, die kein gültiges JSON ist, sudo in einem Build-Schritt, MAINTAINER und ein COPY ., das den Cache der Dependency-Installation zerstört. Jede Regel hat auf dieser Seite ihren eigenen Unterabschnitt mit Vorher-Nachher-Snippet.

Nein, und es versucht es auch nicht zu sein. hadolint ist eine Binary, die Sie installieren, und es kombiniert eigene Regeln mit allem, was ShellCheck über die Shell in Ihren RUN-Zeilen sagt — ausgezeichnet in der CI und die richtige Antwort, wenn Sie eine Toolchain vorschreiben können. Dies hier ist der Fall, den hadolint nicht abdeckt: ein Dockerfile, das Sie jetzt geprüft haben wollen, aus dem Browser, auf einer Maschine, auf der Sie nichts installieren dürfen, ohne die Datei an einen Server zu schicken. Siebzehn Regeln statt eines langen Schwanzes, jede mit einem Fix, und weiter unten eine ausdrückliche Liste dessen, was der Linter bewusst nicht meldet.

Nein. Der Parser und alle siebzehn Regeln sind JavaScript, das in Ihrem Tab läuft — es gibt keinen Server, keinen API-Aufruf und kein Logging, also werden 0 Bytes hochgeladen. Das zählt hier mehr als bei den meisten Tools: Dockerfiles enthalten interne Registry-Hostnamen, private Paket-Indizes, Build-Argumente und gelegentlich ein Credential, das niemand hätte committen sollen.

Weil jedes RUN ein eigenes Layer mit einem eigenen Cache-Eintrag ist. Wenn Sie später nur die Install-Zeile ändern, verwendet Docker das gecachte Update-Layer weiter — es kann Wochen oder Monate alt sein — und führt die Installation gegen diesen veralteten Paket-Index aus. Das Ergebnis ist entweder eine unerwartete Version oder ein 404 für ein Paket, das inzwischen ersetzt wurde. Beides in einem RUN zu verbinden lässt Index und Installation denselben Cache-Eintrag teilen, sodass sie sich nie widersprechen können.

Nichts — bis der Publisher pusht. :latest ist ein wandernder Zeiger, und ein FROM ohne Tag bedeutet :latest. Das Basis-Image, das Ihr Build letzten Monat verwendet hat, und das von heute Nacht können also unterschiedliche Major-Versionen sein, ohne dass Sie etwas geändert haben — und nichts in der Datei hält fest, welche Sie tatsächlich getestet haben. Pinnen Sie einen Tag, den Sie getestet haben. Wenn Sie byte-identische Rebuilds brauchen, ergänzen Sie den Digest (@sha256:…). Dieser Linter verlangt einen Tag und nie einen Digest: diese Abwägung bleibt Ihre.

Weil Docker es nicht ablehnt — es tut stillschweigend etwas anderes. Die Exec-Form wird nur verwendet, wenn das Argument als JSON parst, und JSON verlangt doppelte Anführungszeichen. Mit einfachen Anführungszeichen fällt das Ganze auf die Shell-Form zurück, Docker führt also /bin/sh -c "['nginx']" aus, und Klammern und Anführungszeichen werden Teil des Kommandos. Zur Build-Zeit warnt nichts; der Container startet einfach nicht — oder startet eine Shell, die nicht der Prozess ist, den Sie als PID 1 gemeint haben.

Nicht direkt, und er sagt das lieber, als zu raten. Eine .dockerignore ist eine separate Datei, ein Linter, der nur das Dockerfile liest, kann also nicht wissen, ob node_modules, .git und Ihre Build-Artefakte in den Build-Kontext wandern. Was er kann: das Muster erkennen, das eine fehlende .dockerignore teuer macht — ein COPY . vor der Dependency-Installation, das ist DF017 — und den .dockerignore-Hinweis in den Fix-Text dieser Regel legen.

Beides wurde geprüft und bewusst verworfen. EXPOSE ist Dokumentation: es veröffentlicht von sich aus nichts, ein fehlendes bricht also nichts und ein zusätzliches kostet nichts. HEALTHCHECK wird von Kubernetes komplett ignoriert, und dort laufen diese Images in der Praxis — es zu melden würde dieses Publikum darauf trainieren, den Linter zu ignorieren. Die vollständige Liste dessen, was still bleibt, mit Begründung für jeden Punkt, steht im Abschnitt „Was er bewusst nicht meldet“ über den FAQ.

Bis zu 200.000 Zeichen — grob viertausend Zeilen dichter Text, also zwei Größenordnungen mehr als jedes echte Dockerfile. Darüber verweigert er mit einer Meldung, statt Ihren Tab einzufrieren, denn eine so große Eingabe ist ein Build-Log oder ein gepacktes Archiv, kein Dockerfile. Auch die Funde sind begrenzt, auf 20 pro Regel und 200 insgesamt, und das Panel nennt die Grenze und die echte Anzahl, sobald eine greift.

More free, private DevOps tools.

Der Dockerfile Linter ist ein Tool in OpsCanopy — einem wachsenden Blätterdach browserbasierter Validatoren, Konverter und Tester, die keinen Server berühren.

39 free tools, every one offline-capable — opscanopy.com works with no signup and nothing uploaded.

Verwandt: Docker Run zu Compose verwandelt ein Container-Kommando in eine Service-Datei, Env Example Checker findet die Variablen, die ein Image erwartet, der GitHub Actions Validator und der GitLab CI Validator prüfen die Pipeline, die das Image baut, und der JSON ↔ YAML Konverter hilft, wenn die Konfiguration daneben umgeformt werden muss — oder stöbern Sie im vollständigen Tools-Verzeichnis.

Ohne Gewähr zur Bequemlichkeit bereitgestellt; prüfen Sie kritische Konfiguration immer gegen das Werkzeug, das sie später verarbeitet. OpsCanopy ist kostenlos und offen.