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)+pkgutilauto-discovery of everything inwhalint/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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.