Dockerfile Linter
Review Dockerfiles for suspicious build patterns, stage overrides, cache issues, secrets, and runtime defaults.
Review covers common instructions, multi-stage boundaries, backslash or backtick continuation from an active # escape=directive, and simple RUN heredocs.
Dockerfile findings will appear here.
Static Dockerfile Review Has to Admit What It Cannot See
Dockerfile behavior depends on the Dockerfile frontend, parser directives, base image, build context, BuildKit features, shell, target platform, build arguments, mounted secrets, and files that are not visible in the pasted text. A browser linter can catch suspicious patterns, but it cannot reproduce that entire build.
Warnings, suggestions, and informational findings are kept separate because they carry different levels of confidence. A missing non-root USER may be worth review; it is not automatically a broken image. An invalid duplicate stage name or malformed command form is a different class of problem.
Run Docker's own checks too
Modern Dockerfile syntax includes built-in build checks. When the installed Docker/Buildx version supports them,docker build --check . evaluates the actual parser and selected frontend rather than relying on a standalone text scan.
Parser Directives Can Change How the Same Physical Lines Are Read
# syntax=... selects the Dockerfile frontend. # escape=... can switch the line-continuation escape from backslash to backtick, which is particularly relevant to Windows paths. Docker only recognizes parser directives in the directive area at the top of the file.
An active escape directive changes how continued lines are joined. Reading it before instruction continuation matters especially for Windows Dockerfiles that use backticks around paths containing backslashes.
Multi-Stage Builds Create Several Different Places for “The Last Instruction Wins”
Every FROM begins a build stage. A Dockerfile can have one CMD in the build stage and another in the final runtime stage without conflict. But two CMD instructions inside the same stage do not create two default commands—the later one replaces the earlier one. The same review applies to ENTRYPOINT and HEALTHCHECK.
Stage-aware review keeps those overrides scoped to the stage where they actually occur instead of counting the entire file as one flat list.
ARG and ENV Are Convenient Configuration Channels, Not Secret Stores
Variable names such as API_KEY, PASSWORD, or PRIVATE_KEY are flagged because build arguments and environment instructions can leak through image metadata, history, logs, cache behavior, or downstream layers depending on how they are used.
BuildKit secret mounts exist so a build step can consume a secret without treating it as ordinary Dockerfile configuration. A text linter cannot determine whether an innocent variable name contains a secret, so name-based findings remain warnings rather than proof of exposure.
COPY . Is Usually a Build-Context Question Before It Is a COPY Question
COPY . . can be perfectly reasonable, especially after dependency layers are established. Its safety and cache impact depend heavily on .dockerignore. A pasted Dockerfile does not reveal whether .git, local builds, test data, credentials, or gigabytes of unrelated files are excluded from the context.
A whole-context finding therefore points back to.dockerignore and cache structure instead of declaring every COPY . . pattern wrong.
apt-get update in Its Own Layer Is a Cache Bug Waiting to Happen
Docker's build cache can reuse a previous RUN apt-get update layer even when a later install list changes. Keeping update and install in the same RUN instruction ties the package index refresh to the installation step that needs it.
Removing /var/lib/apt/lists/* in that same layer and avoiding unnecessary recommended packages can also reduce retained package metadata and image size.
Shell Form and Exec Form Trade Convenience for Process Predictability
Docker supports shell and exec forms for RUN, CMD, and ENTRYPOINT. Exec form uses a JSON array and starts the executable directly without an implicit command shell. That usually gives application processes cleaner signal handling and argument boundaries.
Shell form is not forbidden—it is appropriate when shell expansion, pipelines, redirection, or compound shell logic is intentional. The linter reports shell-form CMD/ENTRYPOINT as informational rather than pretending every shell is a vulnerability.
A Version Tag and a Digest Solve Different Reproducibility Problems
An implicit or explicit latest tag says very little about which base image will be pulled next month. A version tag such as node:22-alpine communicates intent but can still be updated by its publisher. A content digest points at immutable image content but requires an explicit update process when you want new fixes.
Deliberate pinning does not require one universal policy. Security maintenance and byte-for-byte reproducibility can pull the policy in different directions.
Docker's Reference Defines the Rules Behind These Findings
The Dockerfile reference defines parser directives, instruction grammar, shell/exec forms, build checks, FROM stages, secret mounts and instruction behavior. Docker's build best practices provides cache, package, context, and image-maintenance guidance. The build checks reference documents Docker's own static checks and their exact scope.
