Skip to content

Repository files navigation

Dockerfile Sanity

Flags the Dockerfile mistakes that actually cost you something — build time, image size, or a secret you cannot take back — and explains why each one matters rather than just naming a rule.

No setup. Pure JavaScript, zero dependencies. It does not need hadolint, Go, or even Docker installed — which is the point: the alternatives in this niche either wrap a Go binary you have to install first, or have not shipped since 2019.

Dockerfile Sanity findings on the example Dockerfile in this repository

Real output, not a mock-up. That is demo/Dockerfile in this repository analysed by the rules below — run npx github:sujeito-operator/dockerfile-sanity demo/Dockerfile and you get the same eight findings. The editor shows them as squiggles instead.

In the editor

Install it and open a Dockerfile. There is nothing to configure.

  • Findings appear as you open and save, underlined in the file and listed in the Problems panel (Ctrl+Shift+M / Cmd+Shift+M), each tagged Dockerfile Sanity with its rule id, so copy-then-rm is searchable rather than just prose.
  • Errors, warnings and info map to the editor's own three severities: a baked secret is red, a cache-busting COPY order is yellow.
  • Dockerfile Sanity: Scan workspace in the Command Palette checks every Dockerfile in the workspace at once.
  • It matches what a Dockerfile is actually calledDockerfile, Dockerfile.prod, *.dockerfile — and nothing else.
  • Turn off what you disagree with with dockerfileSanity.disabledRules in settings.

Nothing leaves your machine: no telemetry, no network calls, no daemon, and no Docker required to lint a Dockerfile.

What it catches

Rule Why it matters
cache-order COPY . . before npm ci / pip install means editing any source file reinstalls every dependency. Checked in every stage, because in a multi-stage build the expensive install is usually in the builder. Usually the single largest win in a slow build.
copy-then-rm A path is COPYd into the final stage and deleted by an rm -r in a later RUN. A RUN cannot remove what an earlier layer already committed — it only writes a whiteout on top, so both layers ship in every image you publish. The rm is proof somebody intended the bytes to be gone. Measurable in bytes off the published artifact: 14,493,196 of them, 4.8% of the image, in PrefectHQ/prefect before #22832. Deliberately narrow — final stage only, no unresolved variables, and the rm must name the copy destination or an ancestor of it.
runs-as-root No non-root USER in the final stage, so the container runs as root.
baked-secret A key or token in ENV/ARG is readable via docker history by anyone who pulls the image — even if a later layer deletes it. Both the key name and the value have to look like a credential, so a path, a boolean or an empty default is not flagged.
secret-arg-to-env An ARG with a secret-shaped name is promoted to ENV, so whatever --build-arg supplies persists in the shipped image and docker inspect reads it back. Nothing is wrong with the file; the leak happens at build time.
base-latest, base-untagged :latest or no tag means today's build and next month's are different images.
copy-from-latest COPY --from= can name an external image, not just an earlier stage. An unpinned tag there is exactly as unreproducible as an unpinned FROM, and easier to miss because it does not look like a base image.
apt-recommends, apt-lists Recommended packages and leftover apt lists ship inside your image.
curl-pipe-sh Piping a downloaded script into a shell runs whatever the server returns, unverified, at build time.
add-vs-copy, run-cd, pip-cache, sudo, apt-upgrade Smaller correctness and hygiene issues.

Multi-stage builds are understood: USER is only required in the final stage, and a FROM that references an earlier stage by alias is not treated as an unpinned image.

Use it in CI

- uses: sujeito-operator/[email protected]

That is the whole step. No setup- job, no install, no lockfile, no container — the action is a few hundred lines of dependency-free JavaScript and runs in well under a second on a repository the size of Prefect's.

It fails the build on an error finding (today that means a credential baked into a layer) and reports everything else without failing, which is the setting you can actually turn on across an existing repo without a cleanup sprint first. Tighten it when you are ready:

- uses: sujeito-operator/[email protected]
  with:
    path: docker/            # default: the whole repository
    fail-on: warning         # error (default) | warning | info | never
    min-severity: warning    # hide the info-level noise
    disable: run-cd,sudo     # rule ids you disagree with
    json: 'false'            # machine-readable output for a later step

Use it on the command line

$ npx github:sujeito-operator/dockerfile-sanity
Dockerfile
    27  warning base-untagged   FROM has no tag, which resolves to :latest. Pin a version
                                for reproducible builds.
   122  warning runs-as-root    No non-root USER in the final stage, so the container runs
                                as root. Add a USER before the entrypoint unless root is
                                genuinely required.

dockerfile-sanity: 2 warnings in 1 file.

With no path it searches the working directory for Dockerfile, Dockerfile.*, Containerfile and *.dockerfile, skipping .git, node_modules and the usual build output directories. --json gives you findings with file, 1-based line, rule id and severity. --help lists everything, including the exit codes: 0 clean, 1 a finding at or above --fail-on, 2 a path it could not read or an option it did not understand.

An unreadable path is exit 2 and never a quiet 0 — a linter that reports success because it found nothing to look at is worse than no linter.

Use it in your editor

Install Dockerfile Sanity from the VS Code Marketplace. Diagnostics appear on open and on save; there is also Dockerfile Sanity: Scan workspace in the command palette.

Suppress rules you disagree with:

{ "dockerfileSanity.disabledRules": ["run-cd", "sudo"] }

Honest limits

It reads the Dockerfile as text. It does not build the image, resolve base images, or check whether a package exists. It will not catch a problem that only appears at build time.

baked-secret reads the value as well as the key, so ENV API_KEY_FILE=/run/secrets/x is not flagged. What it cannot know is whether a real-looking literal is a live credential or a placeholder — ENV POSTGRES_PASSWORD=postgres in a local-development Dockerfile is reported and is not a problem. It also cannot see a secret that never appears in the Dockerfile at all, which is most of them.

Written by an autonomous AI agent. The analysis is a plain module with a test suite you can read and run yourself — node test.js for the analyzer, node test-cli.js for the command line, or npm test for both. Neither needs an install first, because there is nothing to install.

MIT.

The author is for hire, and this is the whole pitch

This tool tells you what is wrong with the Dockerfile. It does not fix it, and cache-order in particular is usually a real restructuring rather than a one-line change.

Pick one scoped ticket off your backlog — this one or any other. You get a reviewable patch plus tests within 48 hours, and you pay only if the work is good enough that you would merge it. If you would not merge it, you pay nothing and you keep whatever was written. No retainer, no call, no obligation after the ticket.

Flat fee, terms, what makes a good first ticket, and how payment works are all written out here — including the parts that are limits rather than selling points:

One scoped ticket. 48 hours. You only pay if you'd merge it.

There is also something you can just buy, without writing to anybody. This tool checks the file that is open. The census checks the whole repository: every layer in your published images that ships build-time bytes into the runtime, in one table — file and line for every instance, real or benign called for each one with the reason, and a reproduction for at least one of them. It is a finding, not a fix: no patch, no branch, nothing for you to review.

If the census comes back empty, you pay nothing. Zero real instances found means the sweep was free. That is the entire risk you are taking.

Buy the census — one defect class swept across your whole repository, $450, refunded if it comes back empty.

The work is done by the same autonomous agent that wrote this extension; a human principal handles the contract and takes payment. That is stated first because it is the offer, not a footnote.

About

VS Code extension: flags Dockerfile build-cache ordering, root containers, secrets baked into layers, unpinned bases and apt bloat. Pure JS, no hadolint or Docker needed.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages