Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,26 @@ jobs:
# No empty report is left behind for an upload step to choke on.
test -z "$SARIF_FILE"
test ! -e fatal.sarif

- name: Lint several Dockerfiles with a configuration
id: multi
uses: ./
with:
version: source
dockerfile: |
testdata/**/minimal.Dockerfile
testdata/invalid/no-from.Dockerfile
config: testdata/config/no-from-off.yaml
sarif-file: multi.sarif

- name: Check the multi-file run
shell: bash
env:
EXIT_CODE: ${{ steps.multi.outputs.exit-code }}
run: |
set -euo pipefail
test "$EXIT_CODE" = 0
# Both files are in one report, and the disabled rule is gone.
grep -q '"uri": "testdata/valid/minimal.Dockerfile"' multi.sarif
grep -q '"uri": "testdata/invalid/no-from.Dockerfile"' multi.sarif
! grep -q '"ruleId": "DKL001"' multi.sarif
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- GitHub Action (`uses: devblac/docker-lint@<tag>`) that installs a
checksum-verified release binary, annotates pull requests and can upload
SARIF to code scanning; tested on Linux, macOS and Windows runners
- Lint several Dockerfiles in one run: pass several paths, a directory (searched
recursively for Dockerfiles) or a glob pattern with `**`; JSON output becomes
an array and SARIF a single run covering every file
- `.docker-lint.yaml` configuration file, found in the current directory or a
parent up to the repository root: ignored rules, per-rule severity overrides
(including `off`), `strict`, `quiet` and `exclude` patterns; `--config` and
`--no-config` flags
- The GitHub Action lints several paths or patterns (one per line) and takes a
`config` input

### Changed
- **Breaking:** rule IDs move from `DLxxxx`, which collide with hadolint's IDs for
Expand Down
71 changes: 62 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,15 @@ go build -o docker-lint ./cmd/docker-lint
## Usage

```bash
docker-lint [flags] [file]
docker-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.

### Flags

| Flag | Short | Description |
Expand All @@ -68,6 +74,8 @@ docker-lint [flags] [file]
| `--quiet` | `-q` | Suppress informational messages (show only warnings and errors) |
| `--strict` | `-s` | Treat warnings as errors (exit code 1 if any warnings) |
| `--ignore <rules>` | | Comma-separated list of rule IDs to ignore |
| `--config <file>` | `-c` | Configuration file (default: the nearest `.docker-lint.yaml`) |
| `--no-config` | | Do not read a configuration file |
| `--rules` | | List all available rules with descriptions |

### Examples
Expand All @@ -76,6 +84,12 @@ docker-lint [flags] [file]
# 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

Expand Down Expand Up @@ -107,13 +121,48 @@ FROM ubuntu:latest
# No USER instruction needed for this build stage
```

### Configuration File

docker-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.

```yaml
# 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`.

## Exit Codes

| 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 - file not found, permission denied, or parse error |
| 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.

## Rules

Expand Down Expand Up @@ -182,7 +231,8 @@ Dockerfile:3:1: [info] DKL202: Found 2 consecutive RUN instructions that could b

### JSON (`--format json`)

Machine-readable JSON output for CI/CD integration:
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:

```json
{
Expand Down Expand Up @@ -228,8 +278,8 @@ The repository's CI workflow runs `go test ./... -cover`. Coverage uploads to Co

### GitHub Actions

The docker-lint action installs a release binary (checksum-verified), lints a Dockerfile and annotates the
pull request diff with the findings:
The docker-lint action installs a release binary (checksum-verified), lints your Dockerfiles and annotates
the pull request diff with the findings:

```yaml
- uses: actions/checkout@v7
Expand All @@ -250,15 +300,18 @@ steps:
- uses: actions/checkout@v7
- uses: devblac/docker-lint@v0.1.0
with:
dockerfile: services/api/Dockerfile
dockerfile: |
Dockerfile
services/**/Dockerfile
strict: true
ignore: DKL404
upload-sarif: true
```

| Input | Default | Description |
|-------|---------|-------------|
| `dockerfile` | `Dockerfile` | Path to the Dockerfile, relative to the repository root |
| `dockerfile` | `Dockerfile` | Files, directories or glob patterns to lint, one per line |
| `config` | | Configuration file (default: `.docker-lint.yaml` at the repository root) |
| `version` | `latest` | docker-lint release to install, e.g. `v0.1.0` |
| `format` | `github` | Log output: `github` (annotations), `text`, `json` or `sarif` |
| `strict` | `false` | Fail on warnings, not only on errors |
Expand All @@ -268,8 +321,8 @@ steps:
| `upload-sarif` | `false` | Upload the SARIF report to GitHub code scanning |

The 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; a Dockerfile that cannot be parsed produces no
report rather than an empty one.
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.

### GitLab CI

Expand Down
38 changes: 31 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ branding:

inputs:
dockerfile:
description: Path to the Dockerfile to lint, relative to the repository root.
description: >-
What to lint, one entry per line: a Dockerfile, a directory searched
recursively for Dockerfiles, or a glob such as "services/**/Dockerfile".
Paths are relative to the repository root.
default: Dockerfile
config:
description: >-
Configuration file. Defaults to the .docker-lint.yaml at the repository
root, when there is one.
default: ""
version:
description: >-
docker-lint release to use: "latest" or a tag such as "v0.1.0". "source"
Expand Down Expand Up @@ -124,6 +132,7 @@ runs:
shell: bash
env:
DOCKERFILE: ${{ inputs.dockerfile }}
CONFIG: ${{ inputs.config }}
FORMAT: ${{ inputs.format }}
STRICT: ${{ inputs.strict }}
QUIET: ${{ inputs.quiet }}
Expand All @@ -138,25 +147,40 @@ runs:
if [ "$STRICT" = true ]; then args+=(--strict); fi
if [ "$QUIET" = true ]; then args+=(--quiet); fi
if [ -n "$IGNORE" ]; then args+=(--ignore "$IGNORE"); fi
if [ -n "$CONFIG" ]; then args+=(--config "$CONFIG"); fi

# One path or pattern per line. Patterns stay quoted: docker-lint
# expands them itself, including ** on every runner OS.
paths=()
while IFS= read -r entry; do
entry="${entry#"${entry%%[![:space:]]*}"}"
entry="${entry%"${entry##*[![:space:]]}"}"
if [ -n "$entry" ]; then paths+=("$entry"); fi
done <<< "$DOCKERFILE"
if [ ${#paths[@]} -eq 0 ]; then
echo "::error::docker-lint: the dockerfile input is empty"
echo "exit-code=2" >> "$GITHUB_OUTPUT"
exit 0
fi

# Record the exit code instead of failing here, so the SARIF report is
# still written and uploaded when the lint finds errors.
code=0
docker-lint ${args[@]+"${args[@]}"} --format "$FORMAT" "$DOCKERFILE" || code=$?
docker-lint ${args[@]+"${args[@]}"} --format "$FORMAT" "${paths[@]}" || code=$?
echo "exit-code=$code" >> "$GITHUB_OUTPUT"

if [ "$UPLOAD_SARIF" = true ] && [ -z "$SARIF_FILE" ]; then
SARIF_FILE=docker-lint.sarif
fi
if [ -n "$SARIF_FILE" ]; then
# A fatal error (exit 2) writes nothing to stdout; an empty report
# would only make the upload fail with a confusing message.
docker-lint ${args[@]+"${args[@]}"} --format sarif "$DOCKERFILE" > "$SARIF_FILE" 2>/dev/null || true
# When no file can be analyzed nothing is written to stdout; an empty
# report would only make the upload fail with a confusing message.
docker-lint ${args[@]+"${args[@]}"} --format sarif "${paths[@]}" > "$SARIF_FILE" 2>/dev/null || true
if [ -s "$SARIF_FILE" ]; then
echo "sarif-file=$SARIF_FILE" >> "$GITHUB_OUTPUT"
else
rm -f "$SARIF_FILE"
echo "::warning::docker-lint: no SARIF report was written because the Dockerfile could not be analyzed"
echo "::warning::docker-lint: no SARIF report was written because no Dockerfile could be analyzed"
fi
fi

Expand All @@ -175,5 +199,5 @@ runs:
case "$EXIT_CODE" in
0) ;;
1) echo "::error::docker-lint found errors (or warnings with strict enabled)"; exit 1 ;;
*) echo "::error::docker-lint could not analyze the Dockerfile (exit code $EXIT_CODE)"; exit "$EXIT_CODE" ;;
*) echo "::error::docker-lint could not analyze every Dockerfile (exit code $EXIT_CODE)"; exit "$EXIT_CODE" ;;
esac
Loading
Loading