A minimal, production-grade CLI tool for statically analyzing Dockerfiles to detect common inefficiencies, anti-patterns, and security issues.
- Static Analysis: Purely static analysis - never executes Docker builds
- Real Dockerfile Syntax: Parsing is delegated to BuildKit, the parser
docker builditself uses, so heredocs (RUN <<EOT), parser directives (# syntax,# escape),RUN --mountflags and line continuations are understood exactly as Docker understands them - Single Binary: Compiles to a single Go binary with no runtime dependencies
- CI/CD Ready: Clear exit codes (0, 1, 2) and JSON output for pipeline integration
- Configurable: Ignore specific rules via CLI flags or inline comments
- Security Focused: Detects secrets in ENV/ARG without exposing actual values
- Multi-stage Support: Correctly analyzes multi-stage Dockerfiles with per-stage rule evaluation
- Comprehensive Rules: 18 built-in rules covering Dockerfile structure, base images, layer optimization, security, and best practices
Prebuilt binaries for Linux, macOS and Windows (amd64 and arm64) are attached to each
release, along with a checksums.txt.
# Linux amd64; replace the version and platform as needed
VERSION=0.1.0
curl -sSLO "https://github.com/devblac/docker-lint/releases/download/v${VERSION}/docker-lint_${VERSION}_linux_amd64.tar.gz"
curl -sSLO "https://github.com/devblac/docker-lint/releases/download/v${VERSION}/checksums.txt"
sha256sum --check --ignore-missing checksums.txt
tar -xzf "docker-lint_${VERSION}_linux_amd64.tar.gz" docker-lint
sudo mv docker-lint /usr/local/bin/Archives are named docker-lint_<version>_<os>_<arch>, with .zip for Windows and
.tar.gz elsewhere.
Requires Go 1.22 or later (as specified in go.mod).
go install github.com/devblac/docker-lint/cmd/docker-lint@latestgit clone https://github.com/devblac/docker-lint.git
cd docker-lint
go build -o docker-lint ./cmd/docker-lintdocker-lint [flags] [path ...]Each path is a Dockerfile, a directory, or a glob pattern. A directory is searched recursively for files named
Dockerfile, Containerfile, Dockerfile.* or *.Dockerfile (skipping .git and node_modules). Patterns
support ** for any number of directories and are expanded by docker-lint itself, so quote them to make them
work the same in every shell, including on Windows. A directory or pattern that matches no Dockerfile is a fatal
error, so a CI job cannot pass without linting anything. With no path, docker-lint reads from stdin.
# Analyze a Dockerfile
docker-lint Dockerfile
# Analyze every Dockerfile in a repository
docker-lint .
# Analyze Dockerfiles matching a pattern
docker-lint 'services/**/Dockerfile'
# Analyze from stdin
cat Dockerfile | docker-lint
# JSON output for CI integration
docker-lint --json Dockerfile > results.json
# Strict mode for CI (fail on warnings)
docker-lint --strict Dockerfile
# Ignore specific rules
docker-lint --ignore DKL101,DKL103 Dockerfile
# Suppress informational messages
docker-lint --quiet Dockerfile
# List all available rules
docker-lint --rulesDisable specific rules for the next line using comments:
# docker-lint ignore: DKL102
FROM ubuntu:latest
# docker-lint ignore: DKL303
# No USER instruction needed for this build stagedocker-lint reads .docker-lint.yaml (or .docker-lint.yml) from the current directory, or from the nearest
parent directory up to the repository root. Use --config to pick another file and --no-config to read none.
# Rules that are never reported.
ignore:
- DKL103
# Change a rule's severity: error, warning, info, or off to disable it.
severity:
DKL303: error # fail the build when the final image runs as root
DKL404: off # disable the rule
# Fail on warnings too (same as --strict).
strict: true
# Hide informational findings (same as --quiet).
quiet: false
# Skip matching files when a directory or pattern is expanded. A pattern without
# a slash matches a file or directory name anywhere, as in .gitignore.
exclude:
- vendor
- "examples/**"Command-line flags win over the file: --strict=false turns off strict: true, and --ignore adds to the
ignored rules. Unknown keys and invalid severities are fatal errors, so a typo does not silently disable the
configuration. A file named explicitly on the command line is always linted, even if it matches exclude.
| Code | Meaning |
|---|---|
| 0 | Success - no errors found (warnings allowed in non-strict mode) |
| 1 | Lint errors found, or warnings found in strict mode |
| 2 | Fatal error - invalid arguments or configuration, file not found, or parse error |
When several files are linted, one that cannot be read or parsed is reported and the others are still linted; the exit code is then 2.
docker-lint includes 18 built-in rules. Rule IDs use the DKL prefix and are grouped by category in hundreds:
DKL0xx structure, DKL1xx base image, DKL2xx layers, DKL3xx security, DKL4xx best practices.
Renamed rule IDs. Before v0.1.0, rules used
DLxxxxIDs, which collide with hadolint's IDs for different rules. The old IDs are not accepted; passing one to--ignoreprints its replacement. See the CHANGELOG for the full mapping.
| ID | Severity | Name | Description |
|---|---|---|---|
| DKL001 | Error | Instruction before FROM | Every build must start with FROM; only ARG may appear before the first FROM. Docker fails to build such a file |
| ID | Severity | Name | Description |
|---|---|---|---|
| DKL101 | Warning | Missing explicit image tag | Always tag the version of an image explicitly to ensure reproducible builds |
| DKL102 | Warning | Using 'latest' tag | Using 'latest' tag can lead to unpredictable builds as the image may change |
| DKL103 | Warning | Large base image | Consider a smaller variant (slim, alpine) for the base of the final image. Build stages are not checked; a final stage built on one is |
| ID | Severity | Name | Description |
|---|---|---|---|
| DKL201 | Warning | Package manager cache not cleaned | Clean package manager cache in the same RUN instruction to reduce image size. apt-get dist-clean and --mount=type=cache count as clean |
| DKL202 | Info | Consecutive RUN instructions | Combine consecutive RUN instructions to reduce the number of layers |
| DKL203 | Warning | Suboptimal layer ordering | Place instructions that change less frequently earlier to optimize layer caching |
| DKL204 | Warning | Package update without install | Combine package update with install in the same RUN instruction to avoid cache issues |
| ID | Severity | Name | Description |
|---|---|---|---|
| DKL301 | Warning | Potential secret in ENV | Avoid storing secrets in ENV instructions as they persist in the image layers |
| DKL302 | Warning | Potential secret in ARG | Avoid storing secrets in ARG instructions as they are visible in image history |
| DKL303 | Warning | Final image runs as root | The final image should not run as root; set a non-root USER in the final stage. Build stages are not checked, and a USER inherited from an earlier stage counts |
| DKL304 | Warning | ADD with URL | Using ADD with URLs is discouraged; use curl or wget in RUN for better control |
| DKL305 | Warning | ADD where COPY would suffice | Use COPY instead of ADD when not extracting archives or fetching URLs |
| ID | Severity | Name | Description |
|---|---|---|---|
| DKL401 | Warning | Multiple CMD instructions | Only the last CMD instruction takes effect; multiple CMD instructions are likely a mistake |
| DKL402 | Warning | Multiple ENTRYPOINT instructions | Only the last ENTRYPOINT instruction takes effect; multiple ENTRYPOINT instructions are likely a mistake |
| DKL403 | Warning | WORKDIR with relative path | Use absolute paths in WORKDIR to avoid confusion about the current directory |
| DKL404 | Info | Missing HEALTHCHECK | Add a HEALTHCHECK instruction to enable container health monitoring (Kubernetes ignores it and uses its own probes) |
| DKL405 | Info | Wildcard in COPY/ADD source | Wildcard patterns in COPY/ADD may include unnecessary files, increasing build context size |
Human-readable output with file path, line number, severity, rule ID, and message:
Dockerfile:1:1: [warning] DKL102: Using 'latest' tag for image 'ubuntu' is not recommended
Suggestion: Pin to a specific version like 'ubuntu:<version>' for reproducible builds
Dockerfile:3:1: [info] DKL202: Found 2 consecutive RUN instructions that could be combined
Suggestion: Combine RUN instructions using '&&' to reduce layers
Machine-readable JSON output for CI/CD integration. A single file (or stdin) produces one object; several files produce an array with one such object per file:
{
"file": "Dockerfile",
"findings": [
{
"rule_id": "DKL102",
"severity": "warning",
"line": 1,
"column": 1,
"message": "Using 'latest' tag for image 'ubuntu' is not recommended",
"suggestion": "Pin to a specific version like 'ubuntu:<version>' for reproducible builds"
}
],
"summary": {
"total": 1,
"errors": 0,
"warnings": 1,
"info": 0
}
}SARIF 2.1.0, the format GitHub code scanning and other security dashboards ingest. The report carries every rule's metadata, so findings show up with their name, description and severity. The exit code is unchanged, so CI still fails on errors while the report is uploaded.
GitHub Actions workflow commands, which GitHub renders as annotations on the changed lines of a pull request. They need no code scanning, so they work in private repositories without GitHub Advanced Security:
::warning file=Dockerfile,line=1,col=1,title=DKL102::Using 'latest' tag for image 'ubuntu' is not recommended
The repository's CI workflow runs go test ./... -cover. Coverage uploads to Codecov are attempted only when a CODECOV_TOKEN secret is configured; otherwise the upload step is skipped while tests still gate the build.
The docker-lint action installs a release binary (checksum-verified), lints your Dockerfiles and annotates the pull request diff with the findings:
- uses: actions/checkout@v7
- uses: devblac/docker-lint@v0.1.0
with:
dockerfile: DockerfileTo also see findings in the repository's Security tab, upload SARIF to code scanning. This needs the
security-events: write permission, and GitHub Advanced Security on private repositories:
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v7
- uses: devblac/docker-lint@v0.1.0
with:
dockerfile: |
Dockerfile
services/**/Dockerfile
strict: true
ignore: DKL404
upload-sarif: trueThe action fails the step with docker-lint's exit code and exposes it as the exit-code output. A SARIF
report is still written and uploaded when the lint fails, and covers every Dockerfile that could be parsed;
when none could, no report is written rather than an empty one.
lint-dockerfile:
script:
- docker-lint --strict Dockerfile
allow_failure: falsestage('Lint Dockerfile') {
steps {
sh 'docker-lint --strict Dockerfile'
}
}See CONTRIBUTING.md for development setup, testing guidelines, and contribution process.
# Clone the repository
git clone https://github.com/devblac/docker-lint.git
cd docker-lint
# Install dependencies
go mod download
# Run tests
go test ./...
# Build
go build -o docker-lint ./cmd/docker-lint
# Run linter on itself
./docker-lint testdata/valid/minimal.Dockerfile