Skip to content
Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Latest commit

Β 

History

235 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

crates.io ・ VS Code
docs ・ site ・ GH action ・ Wasmer ・ comparison ・ β˜•

droast

crates.io

a dockerfile linter that actually has opinions. it catches bad practices and tells you about them in the least diplomatic way possible.

think of it as code review from a senior dev who's seen too many prod incidents and has stopped being polite about it.

the span-aware parser understands heredocs, parser and escape directives, shell and JSON forms, BuildKit flags such as RUN --mount, Windows paths, and PowerShell β€” and reports malformed Dockerfile syntax as DF071.

vs code extension

install from the marketplace and get inline squiggles as you type:

VS Code

droast β€” Dockerfile Linter

code --install-extension ImmanuelTikhonov.droast

the binary is bundled β€” no separate install needed. findings appear in real time with roast messages on hover.

neovim extension

Install the dependency-free Neovim plugin in one command; no plugin manager is required:

curl -fsSL https://raw.githubusercontent.com/immanuwell/dockerfile-roast/main/scripts/install-neovim-extension.sh | sh

Or use the dedicated droast.nvim repository with your preferred plugin manager:

-- lazy.nvim:  { "immanuwell/droast.nvim" }
-- packer.nvim: use "immanuwell/droast.nvim"
-- mini.deps: MiniDeps.add({ source = "immanuwell/droast.nvim" })

It starts automatically with droast on PATH, lint-on-save, and native diagnostics. Override defaults with require("droast").setup({ args = { "--preset", "production" } }).

install

don't want to install anything? pick one:

Docker:

docker run --rm \
  -v "$PWD:/workspace:ro" \
  -w /workspace \
  immanuwell/droast:1.6.1 \
  --no-roast .

replace . with Dockerfile to lint one file. The same image is also available from ghcr.io/immanuwell/droast.

Web: use the droast web linter. it runs in your browser.

Binary: download your platform binary from the latest release.

one-liner (macOS and Linux, detects Homebrew automatically):

curl -fsL ewry.net/droast/install.sh | sh

Homebrew (macOS and Linux):

brew install droast

Cargo (crates.io, builds from source):

cargo install dockerfile-roast

Embedding droast in a Rust tool? See the Rust library docs.

Wasmer (Wasmer Registry, sandboxed WASI command):

wasmer run immanuwell/droast -- --check-dockerignore=false - < Dockerfile

To scan files or a repository, explicitly mount the directory into the sandbox:

wasmer run --volume "$PWD:/workspace" immanuwell/droast -- /workspace

VS Code: use the extension. the binary is included.

Neovim: use the plugin. it needs droast on PATH.

GitHub Actions:

- uses: immanuwell/dockerfile-roast@1.6.1

see the GitHub Action section for inputs.

More commands, CI examples, and output formats: docs.

usage

Need the Rust API instead of the CLI? See the Rust library docs.

# the basics
droast Dockerfile

# recursively discover and lint an entire repository
droast .

# boring mode (no roasts, just facts)
droast --no-roast Dockerfile

# only care about real problems
droast --min-severity warning Dockerfile

# disagree with a rule? valid, we respect it
droast --skip DF001,DF012 Dockerfile

# ci-friendly output
droast --format github Dockerfile    # github actions annotations
droast --format json Dockerfile      # machine-readable
droast --format compact Dockerfile   # one line per finding
droast --format sarif Dockerfile     # SARIF 2.1.0 for GitHub Advanced Security / IDEs

# preview and apply deterministic fixes
droast fixes Dockerfile
droast --fix --dry-run --format diff Dockerfile
droast --fix Dockerfile
droast --fix DF076,DF079 Dockerfile

# inspect effective builds without Docker or Podman
droast invocations .
droast invocations --format json .

When given a directoryβ€”or no path at allβ€”droast recursively discovers Dockerfile, Dockerfile.*, *.Dockerfile, *.dockerfile, Containerfile, and Containerfile.*. It also reads Compose YAML and Docker Bake HCL/JSON files to find non-standard Dockerfile paths and their declared build contexts. In Podman mode, it additionally follows Quadlet .build/.kube units and local-image *.kube.yaml layouts. Repository ignore rules are respected, while hidden project directories such as .devcontainer remain discoverable.

For DF033, Docker mode uses the effective ignore file Docker would use: <Dockerfile>.dockerignore beside the Dockerfile takes precedence over .dockerignore at the build-context root. Podman mode instead prefers .containerignore, then falls back to .dockerignore. Select it with --engine podman or [workflow] engine = "podman". Missing, empty, comment-only, and negation-only ignore files are reported; use --check-ignorefile=false to disable this context check.

safe fixes

Fixing is opt-in. --fix currently changes only four mechanical findings:

rule deterministic change
DF076 Match instruction keywords to the file's first established upper- or lowercase convention
DF078 Lowercase literal TCP and UDP protocol suffixes in EXPOSE
DF079 Match the case-insensitive AS keyword to an unambiguously upper- or lowercase FROM
DF083 Remove the exact redundant FROM --platform=$TARGETPLATFORM flag

Preview the exact patch before changing anything:

droast fixes Dockerfile
droast --fix --dry-run --format diff .

Apply every available safe fix, or select particular fixers:

droast --fix Dockerfile
droast --fix DF076,DF079 Dockerfile

Only reported findings are eligible, so configuration, --only, --skip, severity filtering, and inline suppressions are respected. Ambiguous casing and non-literal protocols are left unchanged. Files are hash-checked, edits must not overlap, and regular files are replaced atomically with their permissions preserved. Stdin, symlinks, and hard-linked files are deliberately not rewritten. After applying fixes, droast lints the updated files and bases its exit status on the findings that remain.

For tooling, droast fixes --format json and droast --fix --dry-run --format json emit the versioned fix protocol with applicability, stable fix ID and version, source hash, impact metadata, original text, replacement text, zero-based UTF-8 byte ranges, and one-based line/column positions. Normal JSON findings include matching fix objects, and SARIF findings include standard artifact replacements plus the complete protocol metadata. See safe deterministic fixes for the complete contract and refusal behavior.

effective build invocations

droast invocations . resolves each distinct local build declared directly, through Compose, or through Bake. It stays offline and daemonless while reporting Dockerfile and context paths, targets, arguments, platform matrices, named contexts, secret and SSH declarations, caches, exporters, attestations, and the effective ignore file.

droast invocations .
droast invocations --format json .

Values retain their definition and environment provenance. Missing substitutions remain explicitly unresolved; sensitive build arguments are redacted without retaining their value. Compose .env values have lower precedence than the process environment. Bake inheritance cycles and conflicting parent definitions are reported. The versioned JSON contract is schemas/droast-build-invocations-v1.schema.json.

configuration

droast works out of the box with zero configuration. for teams that want to commit project-level defaults, drop a droast.toml in the repo root:

# droast.toml β€” all fields optional
preset       = "production"       # minimal | security | performance | production | strict
skip         = ["DF012", "DF022"]
min-severity = "warning"
fail-on      = "warning"          # optional: make warnings fail CI
no-roast     = true

[severity-overrides]
DF020 = "error"

[shellcheck]
mode = "auto" # off (default) | auto | required
exclude = ["SC2086"]

[workflow]
engine = "podman" # docker (default) | podman

droast searches for droast.toml starting from the current directory, walking up to the nearest .git root. CLI flags always take precedence over the file β€” the file just sets the defaults so you don't repeat yourself.

When ShellCheck is installed, mode = "auto" also analyzes shell-form RUN instructions and script heredocs, reporting native SC#### IDs with Dockerfile source locations. The default is off; required makes a missing or failed ShellCheck executable an SC0000 error. You can also select the mode for one invocation with --shellcheck auto.

To keep lint configuration elsewhere, pass its path explicitly:

droast --config .lint/droast.toml Dockerfile

Shared TOML files are also supported: place the same settings under [tool.droast] (for example in pyproject.toml) and pass that file to --config.

To migrate a Hadolint policy, generate an equivalent droast.toml from its YAML configuration:

droast init --from-hadolint .hadolint.yaml

Compatible settings and Hadolint DL rule aliases are imported; Droast reports every setting or rule that has no equivalent so the migration stays reviewable.

Or replace a Hadolint invocation directly, without converting configuration:

droast --hadolint-compatible Dockerfile

Compatibility mode discovers .hadolint.yaml/.hadolint.yml, accepts Hadolint's common configuration environment variables and flags, understands Hadolint inline ignore pragmas, and supports tty, json, checkstyle, codeclimate, gitlab_codeclimate, gnu, codacy, sonarqube, sarif, and junit output. Equivalent checks report their original DL#### identity. Broader checks retain their DF#### identity, and configured rules or settings that are unmatched or behaviorally different are reported on stderr. Run droast --hadolint-compatible --hadolint-compatibility-report for the complete mapping matrix.

Larger teams can add path-specific overrides, rule categories, inherited organization policy, registry and base-image allowlists, required OCI labels, and governed inline suppressions with mandatory reasons and expiration dates:

# droast ignore=DF001 reason="PLAT-142 migration" expires=2026-09-30
FROM alpine:latest

See the complete configuration guide and the copy-paste examples/droast-enterprise.toml. None of these controls are required; zero-config behavior stays unchanged.

custom messages

Optional message overrides add team guidance, migration links, or your own tone to terminal output. They do not change rules, exit codes, or JSON/SARIF/GitHub output.

# personal overrides in your OS config directory
droast messages init

# reviewed, shared overrides for this repository
droast messages init --project

Edit the generated YAML, then run droast again. It reads the file every time. See custom messages for layering, examples, placeholders, and validation.

github action

add droast to any repo in 5 lines:

- uses: immanuwell/dockerfile-roast@1.6.1

full example (.github/workflows/lint.yml):

name: Lint Dockerfiles

on: [push, pull_request]

jobs:
  droast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: immanuwell/dockerfile-roast@1.6.1

findings show up as inline annotations on the PR diff. no configuration required.

available inputs (all optional):

input default description
files Dockerfile file(s) or glob to lint
min-severity config or info info, warning, or error
fail-on config or error Fail on info, warning, or error findings
preset β€” minimal, security, performance, production, or strict
category β€” comma-separated rule categories to run
skip-category β€” comma-separated rule categories to skip
skip β€” comma-separated rule IDs to ignore
no-roast false technical output only, no jokes
no-fail false advisory mode β€” never blocks the build
baseline β€” repository path to a baseline JSON file
only-new false report only findings not present in baseline
engine config or docker build-context conventions: docker or podman
image-tag latest pin to a specific droast release, e.g. 1.6.1

example with options:

- uses: immanuwell/dockerfile-roast@1.6.1
  with:
    files: '**/Dockerfile'
    preset: production
    skip: DF012,DF022
    no-fail: true        # report findings but don't block the PR
    baseline: .droast-baseline.json
    only-new: true       # hide accepted findings; requires baseline

pre-commit

roast Dockerfiles before they even reach CI, via pre-commit:

- repo: https://github.com/immanuwell/dockerfile-roast
  rev: 1.6.1
  hooks:
    - id: droast

the hook runs on Dockerfile, Dockerfile.*, *.Dockerfile, *.dockerfile, Containerfile, and Containerfile.*, while excluding Dockerfile-specific .dockerignore files. pass flags through args as usual:

    - id: droast
      args: [--min-severity, warning, --skip, DF012]

docker

pull from Docker Hub (or use the identical GHCR image) and use immediately, no install needed:

# lint a Dockerfile in the current directory
docker run --rm -v "$(pwd)/Dockerfile":/Dockerfile immanuwell/droast /Dockerfile

# lint any file, anywhere
docker run --rm -v /path/to/your/Dockerfile:/Dockerfile immanuwell/droast /Dockerfile

# pass flags as usual
docker run --rm -v "$(pwd)/Dockerfile":/Dockerfile immanuwell/droast \
    --no-roast --min-severity warning /Dockerfile

or build locally from source:

docker build -t droast .
docker run --rm -v "$(pwd)/Dockerfile":/Dockerfile droast /Dockerfile

the image is published automatically to Docker Hub as immanuwell/droast and to GHCR as ghcr.io/immanuwell/droast on every release tag. Both registries receive the same version, major/minor, and latest tags.

podman

the same OCI image works with rootless Podman. Mount the repository with :Z on SELinux hosts so Podman can relabel it for the container:

podman run --rm \
    -v "$PWD:/workspace:Z" \
    -w /workspace \
    ghcr.io/immanuwell/droast \
    --engine podman .

--engine podman makes DF033 follow Podman’s .containerignore-before-.dockerignore precedence; it does not require a Podman daemon or change Dockerfile parsing. Persist that workflow for a repository with:

[workflow]
engine = "podman"

Podman preprocesses Containerfile.in with CPP. Droast reports DF075 as an informational reminder to lint the generated Containerfile as part of that build workflow.

wasmer

droast is also published as a WASI package in the Wasmer Registry. It uses the same CLI and rule engine as the native binary.

Lint through stdin without granting filesystem access:

wasmer run immanuwell/droast -- \
    --check-dockerignore=false --format compact - < Dockerfile

Lint a mounted repository:

wasmer run --volume "$PWD:/workspace" immanuwell/droast -- /workspace

Wasmer denies host filesystem access unless a directory is mounted. If the repository uses droast.toml, pass --config /workspace/droast.toml after the -- separator.

shell completions

add this once, never mistype --min-severity again:

# bash β€” add to .bashrc
source <(droast completion bash)

# zsh β€” add to .zshrc
droast completion zsh > ~/.zfunc/_droast

# fish β€” add to config.fish
droast completion fish | source

what it catches

85 rules, ngl thats a lot. run droast --list-rules for the full breakdown.

all 85 rules

  Available Rules

  ID       SEVERITY CATEGORIES                         DESCRIPTION
  ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  DF001    WARN     correctness,reproducibility        Use specific base image tags instead of 'latest'
  DF002    ERROR    security                           Do not run as root
  DF011    INFO     performance                        Use multi-stage builds to reduce image size
  DF013    ERROR    security                           Avoid hardcoded credentials in RUN commands
  DF014    ERROR    security                           Avoid hardcoding passwords or tokens in ARG/ENV
  DF020    INFO     security                           Set explicit non-root USER
  DF003    INFO     performance                        Combine RUN commands to reduce layers
  DF004    WARN     performance                        Clean apt/apk cache before it reaches the final image
  DF005    INFO     correctness,reproducibility        Pin package versions for reproducibility
  DF006    WARN     maintainability,performance        Avoid ADD for local files; prefer COPY
  DF007    WARN     performance                        Do not copy the entire build context (COPY . .)
  DF008    INFO     maintainability,performance        Use WORKDIR instead of inline cd commands
  DF009    WARN     correctness,maintainability        Use absolute paths in WORKDIR
  DF010    WARN     security                           Avoid using sudo inside containers
  DF012    INFO     maintainability,reliability        Set HEALTHCHECK for long-running services
  DF017    WARN     maintainability,reliability        Use ENTRYPOINT with CMD for flexible images
  DF018    WARN     correctness,reliability            Avoid using shell form for ENTRYPOINT
  DF019    WARN     correctness,maintainability        Do not use deprecated MAINTAINER; use LABEL instead
  DF022    INFO     maintainability,reliability        Specify EXPOSE for documented ports
  DF023    INFO     correctness,maintainability        Name intermediate stages instead of relying on numeric indexes
  DF024    WARN     correctness,reproducibility        Avoid using :latest in FROM even with aliases
  DF025    WARN     correctness,reliability            Use JSON array syntax for CMD/ENTRYPOINT
  DF026    WARN     maintainability,performance        Avoid broad local COPY to the filesystem root
  DF030    INFO     performance                        Avoid using pip without --no-cache-dir
  DF031    INFO     performance                        Avoid npm install without ci/--production for prod images
  DF032    INFO     maintainability,reliability        Set PYTHONDONTWRITEBYTECODE and PYTHONUNBUFFERED for Python images
  DF033    INFO     performance,security               Use an effective .dockerignore for each build context
  DF034    ERROR    security                           Avoid persistent world-writable chmod modes
  DF035    INFO     maintainability,reliability        Avoid using curl without --fail flags
  DF036    INFO     maintainability,reliability        Avoid Dockerfile with no CMD or ENTRYPOINT
  DF015    ERROR    correctness,reliability            Avoid using apt-get without -y flag
  DF016    INFO     performance                        Use --no-install-recommends with apt-get
  DF021    ERROR    security,supply-chain              Avoid executing unverified remote scripts
  DF027    ERROR    correctness,reliability            Do not use yum without -y flag
  DF028    WARN     performance                        Cache-bust apt-get update
  DF029    WARN     performance                        Avoid apk add without --no-cache
  DF037    ERROR    correctness,maintainability        Dockerfile must begin with FROM, ARG, or a comment
  DF038    WARN     correctness,maintainability        Multiple CMD instructions β€” only the last one takes effect
  DF039    ERROR    correctness,reliability            Multiple ENTRYPOINT instructions β€” only the last one takes effect
  DF040    ERROR    correctness,reliability            EXPOSE port must be in valid range 0-65535
  DF041    ERROR    correctness,reliability            Multiple HEALTHCHECK instructions β€” only the last one applies
  DF042    ERROR    correctness,reproducibility        FROM stage aliases must be unique
  DF043    WARN     correctness,maintainability        zypper install without non-interactive flag
  DF044    WARN     correctness,maintainability        Avoid zypper dist-upgrade in Dockerfiles
  DF045    INFO     performance                        Run zypper clean after zypper install
  DF046    WARN     performance                        Run dnf clean all after dnf install
  DF047    WARN     performance                        Run yum clean all after yum install
  DF048    ERROR    correctness,reliability            COPY with multiple sources requires destination to end with /
  DF049    INFO     correctness,reliability            Review unresolved COPY --from references resembling stage aliases
  DF050    ERROR    correctness,reliability            COPY --from cannot reference the current stage
  DF051    WARN     reproducibility,supply-chain       Pin versions in pip install
  DF052    INFO     reproducibility,supply-chain       Pin versions in apk add
  DF053    WARN     reproducibility,supply-chain       Pin versions in gem install
  DF054    WARN     reproducibility,supply-chain       Pin versions in go install with @version
  DF055    INFO     performance                        Run yarn cache clean after yarn install
  DF056    INFO     maintainability,performance        Use wget --progress=dot:giga to avoid bloated build logs
  DF057    WARN     reliability,security               Set -o pipefail before RUN commands that use pipes
  DF058    INFO     maintainability,performance        Use either wget or curl consistently, not both
  DF059    WARN     correctness,maintainability        Use apt-get or apt-cache instead of apt in scripts
  DF060    INFO     maintainability,reliability        Avoid running pointless interactive commands inside containers
  DF061    INFO     correctness,maintainability        Do not use --platform in FROM unless required
  DF062    INFO     correctness,reproducibility        ENV references may use inherited values
  DF063    WARN     correctness,maintainability        COPY to relative destination requires WORKDIR to be set first
  DF064    WARN     performance                        Use useradd -l with explicitly high UIDs
  DF065    WARN     reproducibility,supply-chain       Enforce configured approved registries
  DF066    WARN     reliability,security               Bash-specific syntax used without a SHELL instruction
  DF067    INFO     maintainability,performance        Reserved: archive extraction policy is context-dependent
  DF068    ERROR    correctness,reliability            FROM, ONBUILD, and MAINTAINER are forbidden as ONBUILD triggers
  DF069    WARN     correctness,reproducibility        Avoid apt-get upgrade / dist-upgrade β€” makes builds non-reproducible
  DF070    WARN     performance                        Avoid broad COPY before package install β€” invalidates Docker layer cache
  DF071    ERROR    correctness,reliability            Dockerfile syntax must be valid
  DF072    ERROR    correctness,security               Suppression directives must satisfy policy
  DF073    ERROR    reproducibility,supply-chain       Base images must satisfy the approved image policy
  DF074    ERROR    correctness,security               Image labels must satisfy the configured schema
  DF075    INFO     correctness,reliability            Containerfile.in must be linted after Podman CPP preprocessing
  DF076    WARN     correctness,reliability            Use a consistent casing style for Dockerfile instructions
  DF077    ERROR    correctness,reliability            Do not COPY or ADD files excluded from the build context
  DF078    WARN     correctness,reliability            Use lowercase protocol names in EXPOSE
  DF079    WARN     correctness,reliability            Match AS casing to FROM in multi-stage builds
  DF082    INFO     correctness,reliability            Use key=value syntax for ENV and LABEL
  DF083    WARN     correctness,reproducibility        Do not set FROM --platform to the default target platform
  DF084    WARN     correctness,reliability            Avoid reserved Dockerfile stage names
  DF085    WARN     correctness,reliability            Use lowercase multi-stage build names
  DF086    ERROR    correctness,reliability            Declare ARG variables used by FROM before the first FROM
  DF087    ERROR    correctness,reliability            Declare Dockerfile variables before using them

  Use --skip DF001,DF002 to suppress specific rules.
  Use --min-severity warning to hide INFO findings.

the greatest hits:

rule crime
DF001 FROM ubuntu:latest β€” pick an actual tag
DF002 running explicitly as root
DF004 apt cache left in the image (you made a trash can)
DF011 shipping the entire build toolchain to prod
DF013 hardcoded credentials in RUN commands
DF021 curl | sh β€” no.
DF028 split apt-get update + install in separate RUN layers
DF034 chmod 777 somewhere in there
DF037 instruction before FROM (invalid Dockerfile)
DF039 multiple ENTRYPOINT instructions
DF046 dnf install without cache cleanup
DF051 pip install without version pins
DF057 pipe in RUN without set -o pipefail
DF059 apt used instead of apt-get in scripts
DF063 COPY to relative path with no WORKDIR set

rule categories: base images Β· security Β· package managers Β· layer hygiene Β· instruction quality Β· service quality Β· python/node specifics

exit codes

0 = no findings at the configured failure level (or --no-fail), 1 = blocking findings found.

--no-fail is useful for advisory CI runs where you want the output but dont want to block the build yet.

license

MIT. enjoy) β˜•

comparison with other tools

droast and Hadolint scan-time and binary-size comparison

benchmark methodology and detailed results

The comparison used droast 1.4.4 and Hadolint 2.14.0 on Ubuntu 24.04.4 LTS x86-64.

Both tools scanned the same lexically sorted set of 321 real-world Dockerfiles containing 49,563 lines. Each tool received three untimed warm-up scans followed by 10 measured scans. Runs were interleaved, and the execution order was reversed on every iteration to reduce ordering bias. One process invocation scanned the entire corpus, configuration was neutralized, and SARIF serialization was included in the elapsed time.

metric droast Hadolint
median full-corpus scan 984.5 ms 3,844 ms
p95 full-corpus scan 1,085 ms 4,615 ms
files per second 326.1 83.5
lines per second 50,343 12,894
Linux x86-64 binary 7.51 MB 54.73 MB

The benchmark measures execution speed and binary size, not detection quality. Finding totals are not directly comparable because the tools have different rule sets, severities, shell-analysis coverage, and parser behavior.


If droast is useful to you, buy me a coffee.

you can also give droast a ⭐️!

About

droast - a dockerfile linter that actually has opinions πŸ”₯

Topics

Resources

Stars

1.1k stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages