← Back to DevBytes

Hadolint: Complete Configuration Guide

What Is Hadolint?

Hadolint is a Dockerfile linter that helps you enforce best practices, avoid common pitfalls, and catch potential security issues in your Docker images before they reach production. Written in Haskell, it parses Dockerfiles into an abstract syntax tree (AST) and then runs a comprehensive set of rules against that structure. Think of it as ESLint or RuboCop, but specifically for Dockerfiles.

The tool ships as a single static binary (or a Docker image) and can be integrated into any development workflowβ€”from manual CLI checks to automated CI pipelines. Hadolint's rule set is largely derived from the Docker best practices guide and community experience, giving you a battle-tested foundation for writing production-grade container definitions.

Why Hadolint Matters

πŸš€ Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Dockerfiles that look correct can still harbor dangerous misconfigurations. Here's what Hadolint protects you against:

By catching these issues staticallyβ€”without building the imageβ€”Hadolint gives you immediate feedback during development, saving time and reducing failed CI builds.

Installation Methods

Hadolint offers multiple installation paths. Choose the one that fits your environment:

Via Package Managers (macOS / Linux)

# macOS (Homebrew)
brew install hadolint

# Linux (Snap)
sudo snap install hadolint

# Arch Linux (AUR)
yay -S hadolint-bin

Download the Binary Directly

# Linux x86_64
curl -sSL -o /usr/local/bin/hadolint \
  https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
chmod +x /usr/local/bin/hadolint

# macOS x86_64
curl -sSL -o /usr/local/bin/hadolint \
  https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Darwin-x86_64
chmod +x /usr/local/bin/hadolint

Run as a Docker Container

# Pipe your Dockerfile through stdin
docker run --rm -i hadolint/hadolint < Dockerfile

# Or mount the file directly
docker run --rm -v "$PWD/Dockerfile:/Dockerfile" hadolint/hadolint /Dockerfile

CI Integrations

Hadolint is available as a pre-built GitHub Action, GitLab CI template, and Jenkins plugin. For example, the GitHub Action usage looks like:

# .github/workflows/hadolint.yml
name: Lint Dockerfiles
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: hadolint/hadolint-action@v3
        with:
          dockerfile: Dockerfile
          config: .hadolint.yaml
          failure-threshold: error

Understanding Hadolint's Rule System

Hadolint ships with roughly 60 built-in rules, each identified by a unique code like DL3000, DL4006, or SC2086. Rules are organized by category:

Each rule has a severity level: error, warning, info, or style. Understanding this taxonomy is crucial for effective configuration, because every aspect of Hadolint's behavior can be tuned through a YAML configuration file.

The Configuration File: .hadolint.yaml

Hadolint automatically detects a configuration file named .hadolint.yaml (or .hadolint.yml) in the current working directory, your home directory, or any parent directory. You can also explicitly specify one with the --config flag:

hadolint --config /path/to/custom-config.yaml Dockerfile

The configuration file is the central mechanism for tailoring Hadolint to your project's needs. Let's explore every available option.

Complete Configuration Reference

Here is a fully-annotated .hadolint.yaml file demonstrating every available setting:

# .hadolint.yaml β€” Complete Configuration Reference

# ───────────────────────────────────────────────
# 1. Global settings
# ───────────────────────────────────────────────

# Fail threshold: exit code behavior
# Options: error | warning | info | style | none
# "error" means only rules with severity "error" cause non-zero exit.
# "none" means never fail (useful for informational CI passes).
failure-threshold: warning

# Trusted registries for FROM image checks (DL3026)
# When Hadolint sees `FROM some-registry/image:tag`, it checks whether
# the registry is in this list. If not, DL3026 fires.
trusted-registries:
  - docker.io
  - my-company.jfrog.io
  - registry.private.example.com

# ───────────────────────────────────────────────
# 2. Per-rule configuration
# ───────────────────────────────────────────────

rules:
  # --- FULLY DISABLE a rule ---
  # Uncomment any rule code to completely turn it off.
  # DL3000: ignore pinned versions for FROM
  # DL3001: ignore clearing of package manager cache
  # DL3002: ignore switching to non-root user
  # DL3003: ignore using WORKDIR with relative paths
  # DL3004: ignore missing apt-get clean
  # DL3005: ignore missing apt-get update
  # DL3006: ignore missing pinned versions in FROM
  # DL3007: ignore using latest tag in FROM
  # DL3008: ignore missing pinned versions in apt-get install
  # DL3009: ignore missing pinned versions in yum install
  # DL3010: ignore missing pinned versions in apk add
  # DL3011: ignore missing pinned versions in pip install
  # DL3012: ignore missing pinned versions in npm install
  # DL3013: ignore missing pinned versions in gem install
  # DL3014: ignore using ADD instead of COPY
  # DL3015: ignore missing pinned versions in FROM for specific stages
  # DL3016: ignore missing pinned versions in FROM for platforms
  # DL3017: ignore missing pinned versions in FROM for manifests
  # DL3018: ignore missing pinned versions in FROM for digests
  # DL3019: ignore using ADD for remote URLs
  # DL3020: ignore using ADD for archives
  # DL3021: ignore COPY with more than two arguments
  # DL3022: ignore COPY with wildcards that could match nothing
  # DL3023: ignore COPY to a path that doesn't end with /
  # DL3024: ignore FROM platform mismatch
  # DL3025: ignore CMD/ENTRYPOINT with arguments in exec form
  # DL3026: ignore using untrusted registries
  # DL3027: ignore using audit utilities
  # DL3028: ignore using latest tag in FROM for specific stages
  # DL3029: ignore using latest tag in FROM for platforms
  # DL3030: ignore using latest tag in FROM for manifests
  # DL3031: ignore using latest tag in FROM for digests
  # DL3032: ignore missing pinned versions in npm install with scope
  # DL3033: ignore missing pinned versions in gem install with source
  # DL3034: ignore missing pinned versions in pip install with extra
  # DL3035: ignore missing pinned versions in pip install with constraints
  # DL3036: ignore missing pinned versions in pip install with requirements
  # DL3037: ignore missing pinned versions in pip install with index
  # DL3038: ignore missing pinned versions in pip install with trusted host
  # DL3039: ignore missing pinned versions in pip install with wheel
  # DL3040: ignore missing pinned versions in npm install with registry
  # DL3041: ignore missing pinned versions in npm install with scope
  # DL3042: ignore missing pinned versions in npm install with tag
  # DL3043: ignore missing pinned versions in npm install with git
  # DL3044: ignore missing pinned versions in npm install with tarball
  # DL3045: ignore missing pinned versions in npm install with local
  # DL3046: ignore missing pinned versions in npm install with remote
  # DL3047: ignore missing pinned versions in npm install with alias
  # DL3050: ignore missing pinned versions in npm install with engine
  # DL3051: ignore missing pinned versions in npm install with os
  # DL3052: ignore missing pinned versions in npm install with cpu
  # DL3053: ignore missing pinned versions in npm install with libc
  # DL3054: ignore missing pinned versions in npm install with runtime
  # DL3055: ignore missing pinned versions in npm install with target
  # DL3056: ignore missing pinned versions in npm install with arch
  # DL3057: ignore missing pinned versions in npm install with platform
  # DL3058: ignore missing pinned versions in npm install with dist
  # DL3059: ignore missing pinned versions in npm install with version
  # DL3060: ignore missing pinned versions in npm install with range
  # DL3061: ignore missing pinned versions in npm install with constraint
  # DL4000: ignore using both CMD and ENTRYPOINT
  # DL4001: ignore using exec form for CMD
  # DL4002: ignore using shell form for ENTRYPOINT
  # DL4003: ignore using CMD with arguments
  # DL4004: ignore using ENTRYPOINT with arguments
  # DL4005: ignore using ADD for remote URLs
  # DL4006: ignore using pipefail in SHELL

  # --- CHANGE SEVERITY of a rule ---
  # Elevate a warning to error, downgrade an error to info, etc.
  # Format: rule-code: severity_level
  DL3000: error          # Unpinned FROM is now a hard error
  DL3003: warning        # Relative WORKDIR as warning
  DL3007: error          # Latest tag is a hard error
  DL3014: info           # ADD instead of COPY is informational
  DL4006: none           # Pipefail check disabled entirely

  # --- IGNORE a rule for specific paths ---
  # When the Dockerfile includes files matching these patterns,
  # the associated rule is suppressed.
  ignores:
    - rule: DL3008
      paths: ["**/node_modules/**", "**/vendor/**"]
    - rule: DL3014
      paths: ["**/test/**"]
    - rule: SC2086
      paths: ["**/legacy/**"]

  # --- TRUST specific labels ---
  # Hadolint can be configured to trust images that have certain labels.
  # This is an advanced feature for organization-wide policies.
  trusted:
    - org.opencontainers.image.ref.name
    - com.example.vendor.security-reviewed

# ───────────────────────────────────────────────
# 3. ShellCheck integration (SC rules)
# ───────────────────────────────────────────────

# Pass options directly to ShellCheck for RUN instruction scripts.
# ShellCheck is bundled inside Hadolint.
shellcheck:
  # ShellCheck binary path (if you want an external one)
  binary: /usr/bin/shellcheck
  # ShellCheck options
  options:
    - "--shell=bash"
    - "--severity=error"
    - "--exclude=SC1090,SC1091"

# ───────────────────────────────────────────────
# 4. Strict mode
# ───────────────────────────────────────────────

# When true, any unrecognized configuration key causes a hard error.
# Useful for catching typos in CI environments.
strict-labels: false

# ───────────────────────────────────────────────
# 5. Formatting output
# ───────────────────────────────────────────────

# Default output format. Overridable via --format flag.
# Options: tty | json | checkstyle | codeclimate | gitlab | sarif
format: tty

# When using JSON or SARIF output, this controls whether the
# "level" field is populated with the severity.
no-color: false

Breaking Down Each Configuration Section

1. failure-threshold

Controls the exit code behavior. By default, Hadolint exits with 1 when any rule violation at error severity or higher is found. Setting failure-threshold to warning makes warnings cause failure too. Setting it to none ensures Hadolint always exits 0β€”perfect for non-blocking lint stages in CI that just collect metrics.

# Example: only hard errors cause failure, warnings are informational
failure-threshold: error

# Example: never fail the build, just report
failure-threshold: none

2. trusted-registries

Rule DL3026 flags any FROM instruction that doesn't use a trusted registry. By default, only docker.io (Docker Hub) is trusted. If your organization uses an internal registry like Artifactory, Quay, or ECR, list it here to suppress DL3026 for those domains.

trusted-registries:
  - docker.io
  - quay.io
  - 123456789012.dkr.ecr.us-east-1.amazonaws.com
  - myregistry.azurecr.io

3. rules β€” Disabling Rules Completely

Simply listing a rule code (without a severity value) disables it entirely. This is useful when a rule consistently produces false positives for your specific use case or when your team has deliberately chosen a pattern that Hadolint flags.

rules:
  # Our base images use a floating tag managed by Renovateβ€”safe to ignore
  - DL3007
  # We intentionally use ADD for tar archives with automatic extraction
  - DL3019
  # ShellCheck false positive in our specific script
  - SC2155

4. rules β€” Changing Severity

Map a rule code to a severity string to override its default level. This lets you promote a style-level nit to a hard error (breaking the build) or demote an overly aggressive rule to informational only.

rules:
  # We treat unpinned FROM as a critical security issue
  DL3000: error
  # Shell form for ENTRYPOINT is just a style preference for us
  DL4002: style
  # Completely ignore this one
  DL4006: none

5. rules β€” Path-Based Ignores

The ignores section lets you suppress a rule for Dockerfiles that match specific glob patterns. This is invaluable in monorepos where certain directories have different standards.

rules:
  ignores:
    # Don't require pinned apt-get versions in test Dockerfiles
    - rule: DL3008
      paths: ["**/test/**", "**/testing/**"]
    # Allow ADD in directories that handle external archives
    - rule: DL3019
      paths: ["**/downloads/**"]
    # ShellCheck rule doesn't apply to legacy scripts
    - rule: SC2002
      paths: ["**/legacy-scripts/**"]

6. shellcheck Options

Hadolint embeds ShellCheck and runs it against the shell commands inside RUN instructions. You can pass any ShellCheck flag via the options list. Common use cases: specifying the shell dialect, excluding noisy rules, or setting a custom severity threshold for shell scripts.

shellcheck:
  options:
    - "--shell=bash"
    - "--severity=warning"
    - "--exclude=SC1090,SC1091,SC2034"

7. strict-labels

When enabled, Hadolint will reject any configuration key it doesn't recognize. This catches typos like faliure-threshold or trused-registries that would otherwise be silently ignored. Enable this in CI to enforce configuration correctness.

strict-labels: true

8. format

Controls output formatting. The default tty format produces colored terminal output. For CI integrations, use checkstyle (Jenkins), gitlab (GitLab CI code quality), codeclimate (Code Climate platform), or sarif (GitHub code scanning).

format: sarif

Inline Ignore Comments

Beyond the configuration file, Hadolint supports per-line suppression directly in Dockerfiles using comments. This is the finest-grained control mechanism and is essential when a rule fires on a line that is genuinely intentional.

Ignore a Single Rule on a Line

# Dockerfile
FROM ubuntu:latest  # hadolint ignore=DL3007

# You can ignore multiple rules on the same line
RUN apt-get update && apt-get install -y curl  # hadolint ignore=DL3008,DL3009

Ignore All Rules on a Line

# Dockerfile
# The DL:ignore-all pragma suppresses every rule for that line
COPY secret.env /app/  # hadolint ignore=DL:ignore-all

Ignore a Block of Lines

Use paired pragmas to suppress rules across multiple consecutive lines:

# Dockerfile
# hadolint ignore=start=DL3008,DL3009
RUN apt-get update \
    && apt-get install -y curl \
    && apt-get install -y vim \
    && apt-get install -y git
# hadolint ignore=end=DL3008,DL3009

# Now DL3008 and DL3009 are suppressed for those four lines

Important: Inline pragmas must appear as a comment on the same line as the instruction (after a space following the instruction) or on the line immediately preceding it. They are parsed by Hadolint before rule evaluation.

Practical Configuration Examples

Example 1: Strict Production-Grade Configuration

This configuration treats almost everything as an error, suitable for regulated environments or security-critical applications:

# .hadolint.yaml β€” Strict production config
failure-threshold: error
strict-labels: true
format: sarif

trusted-registries:
  - docker.io
  - quay.io
  - ecr.production.example.com

rules:
  DL3000: error    # Unpinned FROM
  DL3007: error    # Latest tag
  DL3014: error    # ADD instead of COPY
  DL4000: error    # Both CMD and ENTRYPOINT
  DL4006: error    # Missing pipefail

shellcheck:
  options:
    - "--shell=bash"
    - "--severity=error"

Example 2: Relaxed Development Configuration

For rapid prototyping or internal tooling, you might want warnings without breaking the build:

# .hadolint.yaml β€” Relaxed dev config
failure-threshold: none
format: tty

rules:
  DL3007: warning   # Latest tag is a warning, not error
  DL3014: style     # ADD vs COPY is just a style nit
  DL4000: info      # CMD + ENTRYPOINT is informational

# Disable rules that slow down prototyping
  - DL3008          # Pinned apt-get versions
  - DL3009          # Pinned yum versions
  - DL3011          # Pinned pip versions
  - DL3012          # Pinned npm versions

shellcheck:
  options:
    - "--shell=bash"
    - "--severity=style"
    - "--exclude=SC2002,SC2086,SC2155"

Example 3: Monorepo with Per-Team Policies

In a monorepo, different teams may own different Dockerfile directories with distinct standards:

# .hadolint.yaml β€” Monorepo configuration
failure-threshold: warning
strict-labels: true

trusted-registries:
  - docker.io
  - internal-registry.company.com

rules:
  ignores:
    # Data engineering team uses ADD for archive extraction
    - rule: DL3019
      paths: ["**/data-pipeline/**"]
    # Frontend team uses latest tag for dev images
    - rule: DL3007
      paths: ["**/frontend/dev/**"]
    # Legacy infra team has exceptions
    - rule: DL4006
      paths: ["**/legacy-infra/**"]

shellcheck:
  options:
    - "--shell=bash"
    - "--severity=warning"

Integrating Hadolint Into CI/CD Pipelines

GitHub Actions with SARIF Upload

# .github/workflows/hadolint.yml
name: Dockerfile Lint
on:
  push:
    paths:
      - '**/Dockerfile'
  pull_request:

jobs:
  hadolint:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4
      - name: Run Hadolint
        uses: hadolint/hadolint-action@v3
        with:
          dockerfile: |
            Dockerfile
            frontend/Dockerfile
            backend/Dockerfile
          config: .hadolint.yaml
          format: sarif
          output-file: hadolint-results.sarif
          failure-threshold: error
      - name: Upload SARIF to GitHub
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: hadolint-results.sarif

GitLab CI Integration

# .gitlab-ci.yml
hadolint:
  stage: lint
  image: hadolint/hadolint:latest
  script:
    - hadolint --config .hadolint.yaml --format gitlab Dockerfile > hadolint-report.json
  artifacts:
    reports:
      codequality: hadolint-report.json
    paths:
      - hadolint-report.json

Pre-Commit Hook

Catch issues before they even reach a commit by adding Hadolint to your pre-commit configuration:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/hadolint/hadolint
    rev: v2.12.0
    hooks:
      - id: hadolint
        args: [--config, .hadolint.yaml]
        files: ^Dockerfile$

Advanced: Programmatic Configuration via Environment Variables

Hadolint also supports environment variable overrides for certain settings, which is useful in dynamic CI environments where you can't always commit a YAML file:

# Override failure threshold via env var
export HADOLINT_FAILURE_THRESHOLD=error

# Override trusted registries
export HADOLINT_TRUSTED_REGISTRIES="docker.io,quay.io,my-registry.io"

# Override config file path
export HADOLINT_CONFIG=/path/to/custom-config.yaml

# Run with env vars active
hadolint Dockerfile

Note that environment variables take precedence over configuration file settings for the same parameters. This allows CI systems to dynamically tighten or relax rules based on branch context (e.g., stricter on main, looser on feature branches).

Best Practices for Hadolint Configuration

Conclusion

Hadolint transforms Dockerfile authoring from a manual review process into an automated, enforceable standard. Its configuration systemβ€”spanning severity overrides, path-based ignores, trusted registries, ShellCheck integration, and inline pragmasβ€”gives you precise control over exactly what rules apply and how violations are surfaced. By investing time in crafting a thoughtful .hadolint.yaml file tailored to your organization's security posture and development culture, you create a living policy that evolves with your codebase. Whether you're building a single microservice or maintaining hundreds of Docker images across a monorepo, Hadolint's configuration flexibility ensures that your containers start from a foundation of proven best practices, catching anti-patterns before they become production incidents.

πŸš€ Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started β€” $23.99/mo
← Back to all articles