Linting Basics
This section covers linting Dockerfiles with hadolint: the core rules, how to run it locally and in CI, and how to encode team exceptions so warnings stay meaningful.
Search across all documentation pages
This section covers linting Dockerfiles with hadolint: the core rules, how to run it locally and in CI, and how to encode team exceptions so warnings stay meaningful.
RUN scripts.Quick install on macOS:
brew install hadolintOr run it as a container, which needs no local install:
docker run --rm -i hadolint/hadolint < DockerfileRun hadolint against a file and read the numbered rule codes.
hadolint DockerfileDockerfile:5 DL3008 warning: Pin versions in apt install.DL codes are hadolint's own rules; SC codes come from the embedded ShellCheck.Set a threshold so warnings inform without blocking every merge.
hadolint --failure-threshold error Dockerfileerror, warning, info, and style.error blocks on the most serious rules while letting style hints through.The most common early finding is unpinned apt packages.
# Flagged by DL3008
RUN apt-get update && apt-get install -y curl
# Passes DL3008
RUN apt-get update && apt-get install -y --no-install-recommends \
curl=7.88.1-10+deb12u* \
&& rm -rf /var/lib/apt/lists/*--no-install-recommends trims incidental packages and image size./var/lib/apt/lists/* in the same layer avoids shipping the apt cache.RUN so the cleanup actually shrinks the layer.latest tag (DL3007)Floating base tags break reproducibility and provenance.
# Flagged by DL3007
FROM node:latest
# Preferred - pin to a digest
FROM node:22.11.0-bookworm-slim@sha256:<digest>latest can change under you between two identical builds.Relative working directories are ambiguous across stages.
# Flagged by DL3000
WORKDIR app
# Passes
WORKDIR /appWORKDIR.COPY targets become predictable.ADD has surprising behavior that COPY avoids.
# Flagged by DL3020 for a local file
ADD ./app.jar /app/app.jar
# Passes
COPY ./app.jar /app/app.jarADD auto-extracts local tar archives and can fetch URLs, which is easy to misuse.COPY does exactly one thing, so intent is clear.ADD for the rare case where tar extraction is deliberate.Running as root is a security default worth catching in the linter.
RUN useradd --uid 10001 --create-home appuser
USER 10001runAsNonRoot without resolving names.USER before the entrypoint means the process never starts as root.Silence a single finding where the rule genuinely does not apply.
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y build-essentialignore comment applies only to the next instruction.Encode standing exceptions once in .hadolint.yaml at the repo root.
failure-threshold: warning
ignored:
- DL3059 # multiple consecutive RUN - allowed for readability here
trustedRegistries:
- registry.example.com
- docker.io
override:
error:
- DL3007 # never allow :latest.hadolint.yaml in the working directory.trustedRegistries fails builds that pull from unapproved sources.override promotes chosen rules to hard errors for the whole team.Run the linter as a required, early gate before the build.
name: image-lint
on: [pull_request]
jobs:
hadolint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint Dockerfile
run: docker run --rm -i hadolint/hadolint hadolint - < Dockerfilepull_request gives feedback before merge.Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Reviewed by Chris St. John·Last updated Jul 16, 2026