Dockerfile Linter

Lint a Dockerfile for best-practice violations and security issues

Free in-browser Dockerfile linter implementing hadolint rules. Paste a Dockerfile and instantly flag unpinned base images, apt-get without --no-install-recommends, ADD vs COPY misuse, running as root, missing HEALTHCHECK, shell-form CMD and more. Nothing is uploaded. It runs free in your browser on Gera Tools, with nothing uploaded.

Last updated Source: Gera Tools

Which rules does this linter check?

It ports the most actionable hadolint rules: unpinned or latest base images (DL3006/DL3007), apt-get and apk hygiene (DL3009/DL3015/DL3019), using cd instead of WORKDIR (DL3003), ADD vs COPY (DL3020), running as root (DL3002), missing HEALTHCHECK (DL3057), shell-form CMD/ENTRYPOINT (DL3025), sudo (DL3004) and unguarded download pipes (DL4006).

The Dockerfile Linter checks a pasted Dockerfile against widely-used best-practice and security rules — the same kinds of issues hadolint flags — and explains each finding so you can fix it. Everything runs in your browser.

How it works

The linter first tokenises the Dockerfile into instructions, joining lines that end with a backslash continuation and skipping comment lines. It then applies a rule set to each instruction and to the file as a whole:

  • Base image pinning — warns on untagged images and on the latest tag, which is not reproducible.
  • Package manager hygiene — flags apt-get install without --no-install-recommends, apt-get update in its own layer, missing apt list cleanup, and apk add without --no-cache.
  • Layer and path correctnesscd inside RUN (use WORKDIR), relative WORKDIR paths, and ADD where COPY is the safer choice.
  • Security — running as root at the final stage, use of sudo, and piping a curl/wget download straight into a shell without pipefail.
  • Runtime correctness — shell-form CMD/ENTRYPOINT (use the JSON exec form so signals are forwarded) and a missing HEALTHCHECK.

Before and after examples

Unpinned base image:

# Before — flagged
FROM node:latest

# After — pinned
FROM node:22-slim

apt-get hygiene:

# Before — three issues: update alone, no --no-install-recommends, no list cleanup
RUN apt-get update
RUN apt-get install curl git

# After — all three fixed in one layer
RUN apt-get update && apt-get install -y --no-install-recommends \
    curl \
    git \
  && rm -rf /var/lib/apt/lists/*

Shell-form CMD (loses signal forwarding):

# Before — shell form wraps CMD in /bin/sh -c; SIGTERM hits sh, not your app
CMD node server.js

# After — exec form; signals reach the process directly
CMD ["node", "server.js"]

Running as root:

# Before — process has root inside the container
COPY . .
CMD ["node", "server.js"]

# After — switch to the built-in non-root user before CMD
COPY . .
USER node
CMD ["node", "server.js"]

Why shell-form CMD matters for signal handling

When you run CMD node server.js, the shell interprets it as /bin/sh -c "node server.js". Docker sends SIGTERM to PID 1 when stopping a container — that is the shell process, which may not forward the signal to your app. Your Node server then receives a SIGKILL after the stop timeout and cannot do graceful shutdown. The exec form CMD ["node", "server.js"] makes your app PID 1, so it receives SIGTERM directly.

Notes

Severities are advisory: error marks things that are almost always wrong, warning marks strong best practices, and info marks size or hardening optimisations. This linter is a quick preview — keep hadolint in CI for full rule coverage and its shellcheck integration for complex RUN scripts.