Skip to main content
Suggests using heredoc syntax for multi-command RUN instructions.
PropertyValue
SeverityStyle
CategoryStyle
DefaultEnabled
Auto-fixYes (--fix)

Description

Suggests converting multi-command RUN instructions to heredoc syntax for better readability. This rule targets Dockerfile here-documents with RUN, which are supported by BuildKit syntax. Shell-specific heredoc bodies are supported too:
  • POSIX shells keep the usual multi-line heredoc body with set -e and optional set -o pipefail
  • PowerShell heredocs use a multi-line body with $ErrorActionPreference = 'Stop' plus explicit guards between commands. This applies to powershell on Windows and pwsh on cross-platform images.
  • cmd.exe heredocs are supported, but real WCOW builds only executed chained bodies reliably when the body stayed on one logical line, so the fixer emits a grouped single-line (...) command list inside the heredoc
Detects two patterns:
  1. Multiple consecutive RUN instructions that could be combined
  2. Single RUN with chained commands via && (3+ commands by default)

Why heredoc?

Heredoc syntax for RUN instructions offers:
  • Readability: Each command on its own line, no && or \ clutter
  • Maintainability: Easy to add, remove, or reorder commands
  • Debugging: Clear line numbers in error messages

Examples

Before (violation)

RUN apt-get update && \
    apt-get install -y --no-install-recommends ca-certificates curl tzdata && \
    rm -rf /var/lib/apt/lists/*

After (fixed with —fix)

RUN <<EOF
set -e
apt-get update
apt-get install -y --no-install-recommends ca-certificates curl tzdata
rm -rf /var/lib/apt/lists/*
EOF

Another real-life example

RUN mkdir -p /app /var/log/myapp && \
    addgroup -S app && \
    adduser -S -G app app && \
    chown -R app:app /app /var/log/myapp
RUN <<EOF
set -e
mkdir -p /app /var/log/myapp
addgroup -S app
adduser -S -G app app
chown -R app:app /app /var/log/myapp
EOF

Why set -e?

Heredocs don’t stop on error by default - only the exit code of the last command matters. Adding set -e preserves the fail-fast behavior of && chains. See moby/buildkit#2722 for details. For non-POSIX shells, the fixer preserves the same intent with shell-native behavior instead of set -e:
  • PowerShell gets $ErrorActionPreference = 'Stop' and $PSNativeCommandUseErrorActionPreference = $true
  • cmd.exe keeps the original && semantics inside a grouped command block

Options

OptionTypeDefaultDescription
min-commandsinteger3Minimum commands to trigger (heredocs add 2 lines overhead)
check-consecutive-runsbooleantrueCheck for consecutive RUN instructions
check-chained-commandsbooleantrueCheck for && chains in single RUN

Configuration

[rules.tally.prefer-run-heredoc]
severity = "style"
min-commands = 3
check-consecutive-runs = true
check-chained-commands = true

Rule Coordination

When this rule is enabled, hadolint/DL3003 (cd → WORKDIR) will skip generating fixes for commands that are heredoc candidates, allowing heredoc conversion to handle cd correctly within the script. On Windows, this rule also collaborates with tally/powershell/prefer-shell-instruction:
  • if repeated RUN powershell ... or RUN pwsh ... wrappers are first normalized into a PowerShell SHELL, this rule will then see the rewritten RUN instructions under the effective PowerShell shell
  • that lets a cmd-style RUN powershell ... && ... sequence become a proper PowerShell heredoc instead of falling back to a cmd.exe heredoc body
  • the same pass can also absorb immediately following PowerShell-safe RUN instructions, so a stage can end up with one larger PowerShell heredoc after the SHELL rewrite

References