> ## Documentation Index
> Fetch the complete documentation index at: https://tally.wharflab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# tally/prefer-run-heredoc

> Suggests using heredoc syntax for multi-command RUN instructions.

Suggests using heredoc syntax for multi-command RUN instructions.

| Property | Value         |
| -------- | ------------- |
| Severity | Style         |
| Category | Style         |
| Default  | Enabled       |
| Auto-fix | Yes (`--fix`) |

## Description

Suggests converting multi-command RUN instructions to heredoc syntax for better readability.

This rule targets Dockerfile
[here-documents](https://docs.docker.com/reference/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)

```dockerfile theme={null}
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)

```dockerfile theme={null}
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

```dockerfile theme={null}
RUN mkdir -p /app /var/log/myapp && \
    addgroup -S app && \
    adduser -S -G app app && \
    chown -R app:app /app /var/log/myapp
```

```dockerfile theme={null}
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](https://github.com/moby/buildkit/issues/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

| Option                   | Type    | Default | Description                                                 |
| ------------------------ | ------- | ------- | ----------------------------------------------------------- |
| `min-commands`           | integer | 3       | Minimum commands to trigger (heredocs add 2 lines overhead) |
| `check-consecutive-runs` | boolean | true    | Check for consecutive RUN instructions                      |
| `check-chained-commands` | boolean | true    | Check for `&&` chains in single RUN                         |

## Configuration

```toml theme={null}
[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

* [Dockerfile here-documents](https://docs.docker.com/reference/dockerfile/#here-documents)
* [Introduction to heredocs in Dockerfiles](https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/)
* [moby/buildkit#2722](https://github.com/moby/buildkit/issues/2722)
