forkcloser/godolint

hadolint, ported to Go.

godolint lints Dockerfiles. It implements hadolint's rules, with the same codes, severities and messages, as a pure Go library with no binary dependencies, plus a small command-line wrapper.

Library

go get github.com/forkcloser/godolint@v0.3.0

Command line

go install github.com/forkcloser/godolint/cmd/godolint@v0.3.0
Dockerfilegodolint Dockerfile
  1. FROM node:latest
    • DL3007warning Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag
  2. RUN apt-get install -y curl
    • DL3008warning Pin versions in apt get install. Instead of apt-get install <package> use apt-get install <package>=<version>
    • DL3015info Avoid additional packages by specifying --no-install-recommends
  3. ADD ./src /srv/app
    • DL3020error Use COPY instead of ADD for files and folders
  4. WORKDIR app
    • DL3000error Use absolute WORKDIR
  5. RUN curl -sSL https://get.example.com | sh
    • DL4006warning Set the SHELL option -o pipefail before RUN with a pipe in it. If you are using /bin/sh in an alpine image or if your shell is symlinked to busybox then consider explicitly setting your SHELL to /bin/ash, or disable this check
  6. CMD npm start
    • DL3025warning Use arguments JSON notation for CMD and ENTRYPOINT arguments
7 findings: 2 errors, 4 warnings, 1 infoexit status 1
Output format
[{"file":"Dockerfile","line":4,"column":1,"level":"error","code":"DL3000",
  "message":"Use absolute WORKDIR"},
 {"file":"Dockerfile","line":1,"column":1,"level":"warning","code":"DL3007",
  "message":"Using latest is prone to errors if the image will ever update. …"},
 …]

A JSON array on stdout, one object per finding. The example above is checked against the binary's output by this site's test recipe.

Why a port

hadolint is written in Haskell and distributed as a binary. A Go project using it has to download that binary per platform, verify it by hand, keep it out of every audit that walks the module graph, and parse its output from a child process. godolint is a Go module, so it is pinned in go.mod, checksummed in go.sum, covered by govulncheck and go-licenses like any other dependency, and called as a function.

Library

The SDK is the primary interface. Construct a linter, pass it the contents of a Dockerfile, and read the violations.

import "github.com/forkcloser/godolint/sdk"

linter := sdk.New(
    sdk.WithRuleSet(sdk.RuleSetRecommended),
    sdk.WithDisabledRules("DL3008"),
)

result, err := linter.Lint(ctx, dockerfile)
if err != nil {
    return err
}

for _, v := range result.Violations {
    fmt.Printf("%s line %d: %s (%s)\n",
        v.Severity, v.Line, v.Message, v.Code)
}

if result.HasErrors() {
    os.Exit(1)
}

RuleSetAll is the default. RuleSetRecommended keeps the error and warning severities only. WithShellcheck() enables checking RUN scripts with a shellcheck binary; it is off unless set.

Command line

The command lints the files given as arguments and prints JSON. It exits with status 1 when there are findings.

# Lint one or more files
godolint Dockerfile

# Ignore specific rules
godolint --ignore DL3008 --ignore DL3015 Dockerfile

# Do not run shellcheck, even if one is on PATH
godolint --without-shellcheck Dockerfile

# Pass a configuration file to shellcheck
godolint --shellcheck-rcfile .shellcheckrc Dockerfile

# Ignore "# hadolint ignore=DLxxxx" pragmas
godolint --disable-ignore-pragma Dockerfile

Inline pragmas use hadolint's syntax, so existing Dockerfiles keep their exceptions:

# hadolint ignore=DL3008
RUN apt-get install -y curl

Rules

Rule metadata and the test corpus are generated from hadolint 2.15.1's source. godolint passes hadolint's unit tests and reports the same code, severity and message for each rule.

  • 14 error
  • 43 warning
  • 11 info
  • 1 style
  • 2 off by default
CodeSeverityMessage
DL3000errorUse absolute WORKDIR
DL3007warningUsing latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag
DL3020errorUse COPY instead of ADD for files and folders
DL3062warningPin versions in go. Instead of go install <package> use go install <package>@<version>
DL3059infoMultiple consecutive RUN instructions. Consider consolidation.
DL4000errorMAINTAINER is deprecated

Full list of rules

Relationship with hadolint

hadolint is the upstream. Proposals for new core rules belong there; godolint follows hadolint's rule set and does not add its own. Parsing uses buildkit, the parser Docker itself uses.

Shellcheck

hadolint checks the shell in RUN instructions by invoking shellcheck. godolint can do the same, tracking ENV, ARG, SHELL and multi-stage FROM the way hadolint does. In the SDK it is an explicit opt-in, since it reintroduces a dependency on an external binary. The command line enables it when a shellcheck is found on PATH, and --without-shellcheck turns it off. Running shellcheck on scripts directly, outside the Dockerfile linter, is the recommended setup.