Skip to main content

whalint 🐋

A tiny, zero-dependency Dockerfile linter where every rule is one small file.

Docker's defaults are famously wrong for production: root user, :latest tags, shell-form CMD that eats SIGTERM, apt caches shipped in layers. whalint ships 20 rules that catch them — and is built so that contributing rule #21 is a ~30-line file plus two fixtures, with zero edits anywhere else.

$ whalint Dockerfile
Dockerfile:1: WL001 base image is not pinned (':latest' or no tag) (pin 'node:latest' to a specific tag or digest)
Dockerfile:1: WL003 final image runs as root; add a non-root USER (no USER instruction after the final FROM)
Dockerfile:7: WL015 sudo inside RUN
Dockerfile:10: WL020 CMD/ENTRYPOINT in shell form; use JSON (exec) form
...
15 findings in 1 file.

Install

pip install whalint          # no dependencies, pure stdlib

Or just vendor it — it is stdlib-only Python ≥ 3.9.

Usage

whalint                       # lint every Dockerfile under .
whalint path/to/Dockerfile    # lint one file
whalint --select WL001,WL003  # only these rules
whalint --ignore WL010        # skip a rule
whalint --format json         # machine-readable output
whalint --list-rules          # every rule with its full explanation

Exit codes: 0 clean, 1 findings, 2 usage error. Drop it straight into CI.

Suppressing a finding

# whalint: ignore=WL003
FROM postgres:16          # this instruction only

# whalint: ignore-file=WL010

The rules

Code What it catches
WL001 Base image unpinned (:latest or no tag)
WL002 ADD where COPY suffices
WL003 Final image runs as root (no/root USER)
WL004 apt install without --no-install-recommends
WL005 apt cache (/var/lib/apt/lists) shipped in the layer
WL006 apt upgrade baked into the build
WL007 pip install without --no-cache-dir (ENV-aware)
WL008 Secret-looking values in ENV/ARG
WL009 curl | sh — unverified remote code execution
WL010 Ports exposed but no HEALTHCHECK
WL011 EXPOSE 22 — sshd in a container
WL012 Deprecated MAINTAINER
WL013 Relative WORKDIR
WL014 cd in RUN instead of WORKDIR
WL015 sudo in RUN
WL016 Duplicate CMD/ENTRYPOINT (only the last wins)
WL017 apk add without --no-cache
WL018 COPY . . — whole build context, cache-buster
WL019 apt install without -y (hangs the build)
WL020 Shell-form CMD/ENTRYPOINT (PID 1 never sees SIGTERM)

whalint --list-rules prints the why for each — every rule carries its own explanation in its docstring.

Writing rule #21

A rule is one file in whalint/rules/. It is auto-discovered — no registration list, no imports to add, no test to write:

# whalint/rules/wl021_npm_install.py
import re

from whalint.registry import rule

_NPM_INSTALL = re.compile(r"\bnpm\s+(install|i)\b")


@rule("WL021", "npm install in an image build; use npm ci")
def npm_install(df):
    """npm install may rewrite the lockfile and resolve different versions
    on different days. npm ci installs exactly what package-lock.json says,
    faster, and fails loudly when the lockfile is stale.
    """
    for ins in df.of("RUN"):
        if _NPM_INSTALL.search(ins.value):
            yield ins

Add tests/fixtures/WL021/bad.Dockerfile and good.Dockerfile, run pytest — the fixture walker picks the new rule up automatically. Full walkthrough in CONTRIBUTING.md.

Design

  • Parser (whalint/parser.py) — line-oriented Dockerfile parser: continuations, comments, # escape= directive, JSON/shell form, inline ignores. As much parser as the rules need and no more.
  • Registry (whalint/registry.py) — @rule(code, message) + pkgutil auto-discovery of everything in whalint/rules/.
  • Engine (whalint/engine.py) — runs rules, applies inline/file/CLI suppressions, sorts findings.
  • Rules yield instructions (or line numbers); the framework does the rest.

License

MIT

Metadata

Release files for whalint 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for whalint 0.1.0
File Size Uploaded Provenance Actions
whalint-0.1.0.tar.gz 16.9 kB With provenance Details for whalint-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for whalint 0.1.0
File Interpreter ABI Platform Actions
whalint-0.1.0-py3-none-any.whl Python 3 none any Details for whalint-0.1.0-py3-none-any.whl

Total release size: 37.1 kB

Release files / whalint-0.1.0.tar.gz

Download URL whalint-0.1.0.tar.gz
Size 16.9 kB
Tags Source
SHA-256 checksum
How to use checksums
c8ac69bdb7c3f03b412cb39ff5b07e37d4eeb73008e990cfa79591a9fd3f6d07
BLAKE2b-256 checksum
How to use checksums
ac79636860c09a287f631b3c541ae5771b8d8442a9ac1fb5de53fdc420c54ee8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

Release files / whalint-0.1.0-py3-none-any.whl

Download URL whalint-0.1.0-py3-none-any.whl
Size 20.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
da9d02c0b1b15d67715d067ace4ad37921b5fb45525de53b1bd95c0e67098793
BLAKE2b-256 checksum
How to use checksums
59067061ac27cd35befe3fbe48debd414c9aff2fedbfe1f2898883651edc6f39
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

Security for whalint 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for whalint-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page