A fast, stack-aware Dockerfile linter that catches image bloat and bad-practice patterns β with stable rule IDs and CI-friendly output.
dockopt parses your Dockerfiles into a real instruction model β stages, multi-line
instructions, heredocs, JSON/exec form, and # escape directives β then runs generic and
stack-specific rules over the result. No regex-on-raw-lines guessing, so rules don't misfire
on comments, casing, or line continuations.
- π― Stack-aware β detects Go, Java, Rust, .NET, PHP, and Ruby and runs targeted rules (plus generic checks for Python, Node.js, and C/C++).
- π§ Real parser β understands stages, continuations, heredocs, and JSON instructions instead of grepping raw lines.
- βοΈ Built for CI β stable rule IDs, a configurable failure threshold, and precise exit codes.
- π¦ Batch + streaming β analyze many files in one run; JSON output is JSON Lines, ready for
jq. - πͺΆ Zero dependencies β a single static Go binary. Fuzz- and race-tested.
Install with Go 1.24 or newer:
go install github.com/davidgrldo/dockerfile-optimizer/cmd/dockopt@latestOr build from source:
git clone https://github.com/davidgrldo/dockerfile-optimizer.git
cd dockerfile-optimizer
go build -o dockopt ./cmd/dockoptThen point it at a Dockerfile:
$ dockopt Dockerfile
Detected stack: go
Stack-specific checks enabled.
[warn] GEN001 (line 1): Avoid using 'latest' tag in base images
[warn] GEN002 (line 2): Add '--no-install-recommends' to 'apt-get install' to avoid pulling optional packages
[warn] GEN003 (line 2): Remove the apt cache in the same RUN (rm -rf /var/lib/apt/lists/*) to keep the layer small
[warn] GO001 (line 1): Consider using multi-stage builds in Go to reduce final image size
[warn] GO003 (line 1): Avoid using golang image in final stage; copy binary to scratch/distroless/alpinedockopt [--json] [--stack <name>] [--fail-on none|warn|error] <Dockerfile>...
Options must appear before the Dockerfile paths:
--jsonwrites the versioned JSON result instead of human-readable output.--stack <name>overrides detection with a validated stack name (applied to every path).--fail-on none|warn|errorselects the failure threshold. The default iserror.
The threshold controls only the process status; findings below the threshold still appear in the output.
./dockopt Dockerfile
./dockopt --json Dockerfile
./dockopt --stack go --fail-on warn DockerfilePass more than one path to analyze a batch; use your shell's globbing to expand patterns:
./dockopt services/*/Dockerfile
./dockopt --json $(git ls-files '*Dockerfile')- Human output prefixes each report with a
==> path <==header. - JSON output is emitted as JSON Lines: one result object (same schema below) per line, including a per-file error envelope for any file that fails to parse. This streams cleanly into
jq -c. - The exit code is the most severe outcome across all paths (
2over1over0), so one unparseable file surfaces as2even when the rest are clean.
dockopt is a single binary with no runtime dependencies, so it drops into any pipeline. A GitHub Actions step that fails the build on any warning or worse:
- uses: actions/setup-go@v5
with:
go-version: "1.24"
- run: go install github.com/davidgrldo/dockerfile-optimizer/cmd/dockopt@latest
- run: dockopt --fail-on warn $(git ls-files '*Dockerfile')0: analysis completed and no finding reached the configured threshold.1: one or more findings reached the configured threshold.2: invalid arguments or stack, parse failure, input error, or output error.
Analysis results are written to stdout. Operational diagnostics are written to stderr.
Go, Java, Rust, .NET, PHP, and Ruby have stack-specific rules. Python, Node.js, and C/C++ are detected but receive generic checks only.
| Stack | Override | Stack-specific rules |
|---|---|---|
| Go | go |
β |
| Java | java |
β |
| Rust | rust |
β |
| .NET | dotnet |
β |
| PHP | php |
β |
| Ruby | ruby |
β |
| Python | python |
β¬ generic checks only |
| Node.js | node |
β¬ generic checks only |
| C/C++ | c_cpp |
β¬ generic checks only |
Generic Dockerfile rules run for every stack.
Rule IDs are stable and safe to reference in CI (e.g. to gate on a subset).
| ID | Severity | Applies to | Checks |
|---|---|---|---|
GEN001 |
warn | all | Base image uses :latest, or is untagged (which defaults to latest). Stage references, scratch, and digest-pinned images are exempt. |
GEN002 |
warn | all | apt-get install without --no-install-recommends. |
GEN003 |
warn | all | apt-get install without clearing /var/lib/apt/lists in the same RUN. |
GEN004 |
warn | all | ADD <url> instead of RUN curl/wget (with a checksum) or COPY. |
GEN005 |
warn | all | Final stage's effective USER is root. |
GO001 |
warn | go | Single-stage Go build (multi-stage shrinks the image). |
GO002 |
error | go | go build for a scratch final image without CGO_ENABLED=0 (checked on the RUN and on stage-level ENV/ARG). |
GO003 |
warn | go | golang image used as the final stage. |
JAVA001 |
info | java | Full JDK base image (openjdk, eclipse-temurin, amazoncorretto) whose tag is not a slim/JRE variant. |
RUST001 |
warn | rust | Single-stage Rust build. |
DOTNET001 |
warn | dotnet | mcr.microsoft.com/dotnet/* base image without an explicit tag. |
PHP001 |
warn | php | composer install without --no-dev. |
PHP002 |
warn | php | composer install without --optimize-autoloader. |
RUBY001 |
info | ruby | bundle install without --deployment. |
Known limits: the
apt-getrules match the commonapt-get install ...form (notapt-get -y install, with the flag before the subcommand), and commands inside heredoc bodies are not analyzed.
Successful JSON output uses schema version 1:
{
"schema_version": "1",
"source": "Dockerfile",
"stack": {
"detected": "go",
"selected": "go",
"supported": true
},
"findings": [
{
"id": "GEN001",
"severity": "warn",
"message": "Avoid using 'latest' tag in base images",
"line": 1,
"end_line": 1,
"stage": 0
}
],
"summary": {
"info": 0,
"warn": 1,
"error": 0
}
}findings is always an array, including clean results. summary counts every emitted finding regardless of --fail-on.
JSON failures use this envelope and exit 2:
{
"schema_version": "1",
"error": {
"kind": "parse_error",
"message": "Dockerfile:4: unterminated JSON instruction"
}
}Error kinds are usage_error, input_error, parse_error, and output_error when output is still possible.
Format, inspect, test, fuzz, and build with:
gofmt -w .
go vet ./...
go test ./...
go test -race ./...
go test ./internal/dockerfile -run=^$ -fuzz=FuzzParse -fuzztime=3s
go build -o dockopt ./cmd/dockoptThe CI workflow runs the same static checks, unit tests, race tests, build, and committed fixture exit contracts. Dependency updates for Go modules and pinned GitHub Actions are proposed monthly through Dependabot.
Dockerfile Optimizer is available under the MIT License.