Check a Dockerfile
Prefer a CLI, or want this in CI before a merge?
Same logic, zero dependencies, one file:
node cli.mjs # scan every Dockerfile under . node cli.mjs /path/to/repo # scan a specific checkout node cli.mjs --format json node cli.mjs --fail-on-risk # exit 2 if any finding exists
Get cli.mjs — Node 18+, nothing to install, no network call ever. --fail-on-risk makes it a one-line CI gate on any Dockerfile change.
What's checked
| Check | How |
|---|---|
| Manifest copied/installed after a broad copy | A dependency manifest (package.json, requirements.txt, go.mod, Gemfile, composer.json, Cargo.toml, ...) is copied and its install command runs, but only after a whole-build-context COPY . . already ran earlier in the same build stage |
| Install with no isolated manifest copy | An install command (npm ci, pip install -r requirements.txt, ...) runs with no earlier instruction naming its manifest file specifically — only a broad copy ever reaches it, so there is no dependency layer to cache at all regardless of order |
| apt update/install split across layers | A RUN instruction runs apt-get update (or apt update) without also running install/upgrade/dist-upgrade in that same instruction — Docker's own Dockerfile best practices name this exact split as the cause of a stale, separately-cached package index |
Checked ecosystems: npm/yarn/pnpm, pip, Go modules, Bundler, Composer, and Cargo. Each build stage (every section between FROM lines) is checked independently, and COPY --from=<stage> is never treated as a broad context copy, since it copies another stage's output rather than your source tree. See why this happens, and this checker's own named limits, or how this compares to reviewing a Dockerfile by eye.
Common questions
Why does putting COPY . . before installing dependencies break the Docker build cache?
Docker caches each instruction's layer and only re-runs an instruction (and everything after it) when its own inputs change. If COPY . . runs before the dependency install step, that COPY layer's cache key includes every file in your source tree — so any source change at all, even a one-line comment, invalidates it and every layer after it, including the install. Copying just the manifest file (package.json, requirements.txt, go.mod) and running the install before copying the rest of the source means the install layer's cache key only changes when the manifest itself changes.
Why does apt-get update need to be in the same RUN as apt-get install?
Docker's own Dockerfile best practices documentation states this directly: a RUN apt-get update by itself is cached as its own layer, and Docker will reuse that cached layer on a later build even though the actual package index it downloaded is now stale. If a later, separately cached RUN apt-get install then runs against that stale cached update, it can resolve versions the package index no longer serves and fail, or silently install older packages than a fresh update would have found.
What if my Dockerfile never copies a manifest file at all?
Then there is no dependency layer to cache in the first place: the only route by which the manifest reaches the image is a broad copy, so any changed file in the build context invalidates the install regardless of instruction order. This checker reports that case separately ("install with no isolated manifest copy") because the fix is different — add a COPY that names the manifest file specifically, before the broad copy, not just reorder existing instructions.
Is anything uploaded?
No. Everything is computed in your browser from the Dockerfile text you paste in, and nothing you type is sent anywhere. The CLI makes no network call either — it only reads files already on your own disk.