Dockerfile Linters Beyond Hadolint
Today I learned about droast, a Rust Dockerfile linter that got a mention in a
video I was watching. Chasing it down turned into a survey of what else is in
this space, because the answer is more crowded than I expected.
droast
The repo is immanuwell/dockerfile-roast.
Its gimmick is tone: it prints the finding along with a snarky remark about it,
framed as code review from a senior dev who’s seen too many prod incidents.
Under the joke it’s a real linter. 85 rules with IDs like DF001 for
FROM ...:latest, DF002 for running as root, DF013 for secrets in ENV,
and DF037 for instruction ordering. They’re grouped into security,
performance, reproducibility, correctness and maintainability, and a few are
language-aware for Python and Node.
droast Dockerfile
droast --no-roast Dockerfile # the humourless version
droast --min-severity warning Dockerfile
droast --skip DF001,DF012 Dockerfile
droast --format sarif Dockerfile # also json, github, compact
It ships as platform binaries, Homebrew, Cargo, a Docker image, a GitHub Action, a pre-commit hook, a VS Code extension and a Neovim plugin. It also compiles to WASM, so there’s a browser version that lints entirely client-side with nothing uploaded, plus a Wasmer package. MIT licensed, around 1k stars.
The README claims roughly 4x hadolint’s throughput (326 vs 83.5 files/sec) and a much smaller binary (7.5 MB vs 55 MB). That’s the project’s own benchmark so I’d take it as directional, though the Haskell runtime does explain the size gap.
Hadolint
hadolint is the incumbent. Haskell, around 9k stars, been around since 2016, and effectively the default in CI.
The interesting design decision is that it parses the Dockerfile into an AST
and runs rules over that instead of regex-matching lines. Then it pulls the
shell script out of each RUN instruction and hands it to ShellCheck. You end
up with two rule namespaces in one report: DL#### from hadolint (pin your
base image, don’t apt-get upgrade, use WORKDIR instead of cd) and
SC#### inherited from ShellCheck (unquoted variables, useless cat, wrong
test operators).
That inline shell analysis is the bit that’s hard to replicate, and it’s a big
part of why hadolint stayed dominant. It also runs offline with no
vulnerability database to sync, and config lives in .hadolint.yaml for
ignores and trusted registries.
docker build –check
This one I didn’t know about at all. BuildKit has had built-in checks since Buildx 0.15 / Docker Desktop 4.33:
docker build --check .
Zero install, runs as part of the build, and catches things like
JSONArgsRecommended, StageNameCasing, FromAsCasing and
SecretsUsedInArgOrEnv. The rule count is small next to hadolint’s, but it’s
already on every machine that has Docker.
Everything else
The rest of the space splits into three tiers that get conflated a lot.
Dockerfile linters proper, which analyse the source file:
dockerfilelint(Node) and RedHat’sdockerfile_lintwere both popular once. Both are effectively unmaintained now.
Image linters, which analyse the built image rather than the source:
- Dockle checks the resulting image
against the CIS Docker Benchmark. No root user, no credential files baked in,
HEALTHCHECKpresent, no unnecessary setuid binaries. Different input entirely, so it complements hadolint rather than competing with it.
IaC and security scanners that happen to cover Dockerfiles:
- Trivy is the big one.
trivy configdoes Dockerfile misconfiguration checks, and the rest of the tool covers CVEs, secrets, SBOM, Kubernetes manifests and Terraform. If you’re already running it you may not need a separate linter. - Checkov and KICS are policy-as-code IaC scanners with Dockerfile rule packs. Both emit SARIF.
- Snyk IaC and Docker Scout cover similar ground commercially.
- Conftest (OPA/Rego) is the option if you want to write your own Dockerfile policy rather than consume someone else’s ruleset. Semgrep has a Dockerfile mode that works for one-off org-specific rules too.
What I’d actually run
docker build --check for free during builds, hadolint in CI for the deep
ruleset, then Dockle or Trivy against the resulting image.
droast’s realistic pitch over hadolint is speed, binary size and the browser/WASM story. Rule coverage parity is the thing I’d want to spot-check against my own Dockerfiles before swapping anything out.