Paste a Dockerfile and get an instant review against 38 best-practice rules: unpinned or latest base images, apt-get update in its own layer, leftover package caches, missing non-root USER, shell-form CMD, secrets in ENV and more. Every finding links to its line and explains why it matters and how to fix it.
Last updated:
1
Rules (0 enabled)
Untick a rule to ignore it. Your choice is remembered in this browser. You can also add # lint-ignore: DF201 on the line above an instruction.
–
Paste a Dockerfile
Advertisement
Advertisement
How to use the Dockerfile Linter
Paste your Dockerfile into the editor, upload it, or load one of the samples.
Read the score and the counts of errors, warnings and suggestions; the stage cards show how many layers each stage creates.
Open each finding: click the line number to jump to it and expand Why it matters for the reasoning and a corrected example.
Fix and re-check: the Dockerfile is linted again as you type.
Tune the rules if a check does not fit your project, then copy the report for a code review or ticket.
Rule reference
Rules are grouped by severity. Errors break the build or behave differently than intended, warnings affect security, size or reproducibility, and suggestions are good habits.
Code
Rule
Typical fix
DF101
FROM must be the first instruction
Only ARG may come before FROM
DF102
Unknown instruction
Fix the typo or restore the missing \ on the line above
DF103
Invalid JSON in exec form
CMD ["npm", "start"] with double quotes
DF104 / DF105
Duplicate stage name / unknown COPY --from
Unique AS name; refer to earlier stages only
DF106
Several sources need a directory destination
COPY a.txt b.txt ./
DF107 / DF108
Invalid EXPOSE port / relative WORKDIR
EXPOSE 8080, WORKDIR /app
DF201 / DF202
Unpinned or :latest base image
FROM python:3.12-slim
DF203
Container runs as root
USER app in the final stage
DF204 – DF208
apt-get: update with install, cache cleanup, -y, apt vs apt-get, no upgrade
Docker rebuilds a layer when the instruction or the files it copies change, and every layer after it. Order instructions from least to most frequently changed:
Base image with a pinned tag, and system packages installed and cleaned in one RUN.
Dependency manifests only: package.json and package-lock.json, requirements.txt, go.mod and go.sum.
Dependency install: npm ci, pip install --no-cache-dir -r requirements.txt, go mod download.
Source code: COPY . ., with a .dockerignore that excludes .git, node_modules and build output.
Build step, ideally in a separate stage, then copy only the result into a small runtime image.
Runtime settings: USER, EXPOSE, HEALTHCHECK and an exec-form CMD or ENTRYPOINT.
With this order, editing a source file reuses the cached dependency layers and the rebuild takes seconds instead of minutes.
Frequently asked questions
What does a Dockerfile linter check?
It reads each instruction and compares it with widely recommended Dockerfile practices: reproducible base images (pinned tags), small layers (package caches removed in the same RUN), security (a non-root user, no secrets in ENV, no chmod 777), correct syntax (valid JSON in exec form, known instructions, valid EXPOSE ports) and build-cache efficiency (dependency files copied before the source).
Why should apt-get update and apt-get install be in the same RUN?
Docker caches every RUN instruction as a layer. If apt-get update has its own RUN, that layer is reused on later builds even after you add packages to the install line, so apt-get works with old package lists and may fail or install outdated versions. Putting both in one RUN, followed by rm -rf /var/lib/apt/lists/*, refreshes the lists and removes them in the same layer.
Why does the linter want a USER instruction?
Unless a Dockerfile sets USER, the container process runs as root. If the application is compromised, root inside the container makes escaping to the host or tampering with mounted files easier. Create an unprivileged user, install packages first, then switch with USER in the final stage.
What is the difference between shell form and exec form?
Shell form (CMD python app.py) runs the command through /bin/sh -c, so your program is a child of the shell and does not receive SIGTERM when the container stops; Docker waits 10 seconds and then kills it. Exec form (CMD ["python", "app.py"]) starts the program directly as PID 1, so it can shut down cleanly.
How many layers is too many?
Docker supports up to 127 layers in an image, but well before that many layers make pulls slower and images harder to inspect. Only RUN, COPY and ADD add filesystem layers. The linter suggests combining consecutive RUN instructions and reports the final stage when it creates more than 15 layers. Multi-stage builds help most, because only the final stage ends up in the image.
Is this the same as hadolint?
No. This is an independent, browser-based linter with its own rule set (codes DF101 to DF313) inspired by Docker's official best-practice guidance. It does not run ShellCheck on RUN commands. For CI pipelines, a command-line linter is a good complement; this tool is handy for quick reviews and learning why each rule exists.
Can I ignore a rule for one line?
Yes. Put a comment such as # lint-ignore: DF201 on the line directly above the instruction to skip that rule there, or untick the rule in the Rules list to turn it off everywhere. The rule list is remembered in your browser.
Are all the tools on FreeToolCabin tested for quality and accuracy?
Yes! Every tool on our site undergoes automated Q/A testing in real browser environments to ensure high precision, correct output, and zero errors. If you happen to spot a bug or have a suggestion, please contact us at [email protected] and our team will review it right away.