Skip to main content
Hadolint is a widely-used Dockerfile linter. tally implements its rules natively — so you get Hadolint-compatible checks without installing a separate tool — and adds enhancements like auto-fix and smart suppression.
  • DL rules implemented natively by tally
  • Additional rules covered by equivalent BuildKit or tally rules
  • Full Hadolint rule documentation: github.com/hadolint/hadolint/wiki
tally supports the # hadolint ignore=DLxxxx directive format natively, so existing Hadolint suppressions work without any changes. The # hadolint shell=powershell directive is also supported.

Implemented rules

Rules natively implemented by tally. Auto-fixable rules are marked with 🔧.
RuleDescriptionSeverityNotes
hadolint/DL3001For some bash commands it makes no sense running them in a Docker container like ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig.Info
hadolint/DL3002Last user should not be root.Warning
hadolint/DL3003 🔧Use WORKDIR to switch to a directory.WarningAuto-fixable
hadolint/DL3004Do not use sudo as it leads to unpredictable behavior. Use a tool like gosu to enforce root.Error
hadolint/DL3006Always tag the version of an image explicitly.Warning
hadolint/DL3007Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag.Warning
hadolint/DL3010Use ADD for extracting archives into an image.Info
hadolint/DL3011Valid UNIX ports range from 0 to 65535.Error
hadolint/DL3014 🔧Use the -y switch.WarningAuto-fixable
hadolint/DL3020 🔧Use COPY instead of ADD for files and folders.ErrorAuto-fixable
hadolint/DL3021COPY with more than 2 arguments requires the last argument to end with /.Error
hadolint/DL3022COPY --from should reference a previously defined FROM alias.OffOff by default — does not account for --build-context sources
hadolint/DL3023COPY --from cannot reference its own FROM alias.Error
hadolint/DL3026Use only an allowed registry in the FROM image.OffOff by default — requires trusted-registries configuration
hadolint/DL3027 🔧Do not use apt as it is meant to be an end-user tool; use apt-get or apt-cache instead.WarningAuto-fixable
hadolint/DL3030 🔧Use the -y switch to avoid manual input: yum install -y <package>.WarningAuto-fixable
hadolint/DL3034 🔧Non-interactive switch missing from zypper command: zypper install -y.WarningAuto-fixable
hadolint/DL3038 🔧Use the -y switch to avoid manual input: dnf install -y <package>.WarningAuto-fixable
hadolint/DL3043ONBUILD, FROM or MAINTAINER triggered from within ONBUILD instruction.Error
hadolint/DL3045 🔧COPY to a relative destination without WORKDIR set.WarningAuto-fixable
hadolint/DL3046 🔧useradd without flag -l and a high UID will result in an excessively large image.WarningAuto-fixable
hadolint/DL3047 🔧wget without flag --progress will result in excessively bloated build logs when downloading larger files.InfoAuto-fixable
hadolint/DL3057HEALTHCHECK instruction missing.InfoEnhanced: smart suppression for serverless/FaaS and registry-backed check with --slow-checks
hadolint/DL3061Invalid instruction order. Dockerfile must begin with FROM, ARG, or a comment.Error
hadolint/DL4001Either use Wget or Curl but not both.Warning
hadolint/DL4005 🔧Use SHELL to change the default shell.WarningAuto-fixable
hadolint/DL4006 🔧Set the SHELL option -o pipefail before RUN with a pipe in it.WarningAuto-fixable

Enabling off-by-default rules

DL3022 and DL3026 are off by default and must be enabled in .tally.toml:
# Enable DL3026 with trusted registry enforcement
[rules.hadolint.DL3026]
trusted-registries = ["docker.io", "ghcr.io"]
Providing trusted-registries automatically enables the rule with severity = "warning". Set severity explicitly to use a different level.

Covered by BuildKit and tally rules

These Hadolint rules are superseded by an equivalent BuildKit or tally rule. You do not need to enable both — tally’s rule provides the same or better coverage.
Hadolint ruleEquivalent tally ruleNotes
DL3000buildkit/WorkdirRelativePath 🔧Covers absolute WORKDIR enforcement
DL3012buildkit/MultipleInstructionsDisallowed 🔧Covers multiple HEALTHCHECK instructions
DL3024buildkit/DuplicateStageNameCovers duplicate FROM alias names
DL3025buildkit/JSONArgsRecommended 🔧Covers JSON notation for CMD and ENTRYPOINT
DL3029buildkit/FromPlatformFlagConstDisallowed + tally/platform-mismatchbuildkit/FromPlatformFlagConstDisallowed is off by default; tally/platform-mismatch provides a stricter registry-backed check
DL3044buildkit/UndefinedVarCovers referencing an ENV variable in the same ENV statement
DL3059tally/prefer-run-heredoc 🔧Suggests heredoc syntax instead of consolidating consecutive RUN instructions
DL4000buildkit/MaintainerDeprecated 🔧Covers deprecated MAINTAINER instruction
DL4003buildkit/MultipleInstructionsDisallowed 🔧Covers multiple CMD instructions
DL4004buildkit/MultipleInstructionsDisallowed 🔧Covers multiple ENTRYPOINT instructions

Not planned

The following rules are intentionally not implemented. tally promotes BuildKit cache mounts via tally/prefer-package-cache-mounts as the modern alternative to manual cache-cleanup patterns.
RuleDescriptionReason not planned
DL3009Delete the apt-get lists after installing something.tally/prefer-package-cache-mounts is the recommended approach
DL3015Avoid additional packages by specifying --no-install-recommends.tally/prefer-package-cache-mounts is the recommended approach
DL3019Use the --no-cache switch with apk add.tally/prefer-package-cache-mounts is the recommended approach
DL3032yum clean all missing after yum command.tally/prefer-package-cache-mounts is the recommended approach
DL3036zypper clean missing after zypper use.tally/prefer-package-cache-mounts is the recommended approach
DL3040dnf clean all missing after dnf command.tally/prefer-package-cache-mounts is the recommended approach
DL3042Avoid cache directory with pip install --no-cache-dir.tally/prefer-package-cache-mounts is the recommended approach
DL3060yarn cache clean missing after yarn install.tally/prefer-package-cache-mounts is the recommended approach
Cache-cleanup instructions add build-time overhead and produce smaller layers at the cost of slower rebuilds. BuildKit cache mounts (RUN --mount=type=cache) solve the same problem more efficiently by keeping package caches on the host between builds. See tally/prefer-package-cache-mounts for details.

DL3057: HEALTHCHECK instruction missing (enhanced)

tally’s implementation of DL3057 goes beyond Hadolint’s static check with smart suppression and an optional registry-backed resolution path.

Smart suppression (static)

The rule is automatically suppressed when a HEALTHCHECK would not be beneficial:
  • Serverless base images — AWS Lambda (public.ecr.aws/lambda/*, amazon/aws-lambda-*), Azure Functions (mcr.microsoft.com/azure-functions/*), and OpenFaaS watchdog images. These platforms manage function lifecycle externally.
  • Serverless framework entrypoints — When the final stage’s CMD or ENTRYPOINT invokes a known FaaS wrapper (e.g. functions-framework for Google Cloud Functions), including the common exec prefix pattern.
  • Shell-only containers — When the final stage’s CMD or ENTRYPOINT is a bare interactive shell (bash, sh, etc.), the container is not a long-running service.

Registry-backed resolution (--slow-checks)

HEALTHCHECK is inherited from base images at runtime. If a base image defines HEALTHCHECK CMD ..., child images inherit it automatically. tally can resolve this by inspecting the base image registry.
ScenarioFast path (static)With --slow-checks
No HEALTHCHECK CMD in Dockerfile, base has HEALTHCHECKViolation (false positive)Suppressed (inherited from base)
No HEALTHCHECK CMD in Dockerfile, base has no HEALTHCHECKViolationViolation confirmed
HEALTHCHECK NONE in Dockerfile, base has no HEALTHCHECKViolation (generic “missing”)Specific: “HEALTHCHECK NONE has no effect”
When --slow-checks is off, only the fast static check runs.

Migrating from Hadolint

tally is a drop-in replacement for common Hadolint workflows. Existing inline suppression comments work without modification:
# hadolint ignore=DL3006
FROM ubuntu

# hadolint ignore=DL3004,DL3027
RUN apt install curl
Both ignore=DL3006 and ignore=hadolint/DL3006 are valid. You can also use tally’s own directive format:
# tally ignore=hadolint/DL3006
FROM ubuntu

Shell directive

When using base images with non-POSIX shells (e.g., Windows images with PowerShell), declare the shell to disable POSIX-specific rules:
FROM mcr.microsoft.com/windows/servercore:ltsc2022
# hadolint shell=powershell
RUN Get-Process notepad | Stop-Process
Supported non-POSIX shells: powershell, pwsh, cmd / cmd.exe. When a non-POSIX shell is declared, tally automatically disables shell command analysis rules (e.g., DL3004 sudo detection, DL4001 wget/curl detection) and future ShellCheck-based rules. Both # hadolint shell=<shell> and # tally shell=<shell> formats are supported.

ShellCheck rules (SC rules)

ShellCheck rules analyze shell scripts within RUN commands. Implementation has started with native Go reimplementations that use tally’s fix and reporting infrastructure.
RuleDescriptionStatus
SC1040<<- heredoc terminators may only be indented with tabsImplemented 🔧
Additional SC1xxx (syntax/parsing) and SC2xxx (logic/correctness) rules are planned.