Skip to main content
Pretty-prints structured COPY and ADD heredoc bodies, COPY heredocs that contain shell scripts, COPY or ADD heredocs that contain PowerShell scripts, and executable RUN heredoc bodies.
PropertyValue
SeverityStyle
CategoryStyle
DefaultEnabled
Auto-fixYes (safe)

Description

This rule formats heredoc payloads that are copied into JSON, YAML, TOML, XML, or INI files. It detects structured file types from the heredoc destination path extension and uses the repository’s .editorconfig settings for indentation. YAML heredocs can also use max_line_length as a preferred scalar wrapping width. The rule also formats COPY heredocs as shell scripts when the first payload line is a recognized shell shebang, or when the destination path ends in .sh and the payload has no shebang. Plain .sh payloads are parsed as POSIX shell. Unsupported shebangs are skipped instead of falling back to the .sh extension. The rule also formats RUN <<EOF heredocs whose body is executed as a shell script. It uses mvdan.cc/sh/v3, the same formatter library behind shfmt, for Bash, POSIX shell, mksh, Bats, and zsh. Heredocs that are stdin payloads for another command, such as RUN cat <<EOF, are left alone. cmd and unknown shebang bodies are skipped. PowerShell heredocs are formatted with PSScriptAnalyzer’s Invoke-Formatter. This applies to COPY or ADD heredocs targeting .ps1, .psm1, or .psd1 files, and to RUN <<EOF bodies when the active Dockerfile SHELL is PowerShell or the heredoc has a PowerShell shebang. tally starts the PowerShell sidecar lazily, only when slow checks are enabled and a PowerShell heredoc actually needs formatting. For structured payload EditorConfig lookup, tally resolves a virtual filename next to the Dockerfile using the destination basename. For example, a heredoc in services/api/Dockerfile targeting /etc/app/config.yaml is matched as services/api/config.yaml, so selectors such as *.yaml and config.yaml apply naturally. COPY shell heredocs use the same destination-basename lookup, so a target such as /usr/local/bin/entrypoint.sh is matched as entrypoint.sh and can use EditorConfig sections such as *.sh or entrypoint.sh. For RUN shell heredocs, tally resolves a virtual filename next to the Dockerfile named Dockerfile.heredoc.<dialect>, such as Dockerfile.heredoc.sh, Dockerfile.heredoc.bash, or Dockerfile.heredoc.zsh. PowerShell heredocs use PSScriptAnalyzer’s code formatter with command-casing rewrites disabled, so formatting is stable across Linux, macOS, and Windows hosts. They are skipped when slow checks are disabled and do not use EditorConfig today. The formatter also runs as a final auto-fix pass. This means heredocs emitted by other rules, such as tally/prefer-copy-heredoc, are formatted in the same --fix run when this rule is enabled.

Examples

Bad: COPY

FROM alpine:3.20
COPY <<EOF /etc/app/config.json
{"b":2,"a":1}
EOF

Good: COPY

FROM alpine:3.20
COPY <<EOF /etc/app/config.json
{
  "b": 2,
  "a": 1
}
EOF

Bad: RUN

FROM alpine:3.20
RUN <<EOF
set -eu; apk add --no-cache ca-certificates curl openssl; install -d -o app -g app /opt/app/bin /opt/app/cache; chown -R app:app /opt/app
EOF

Good: RUN

FROM alpine:3.20
RUN <<EOF
set -eu
apk add --no-cache ca-certificates curl openssl
install -d -o app -g app /opt/app/bin /opt/app/cache
chown -R app:app /opt/app
EOF

Bad: COPY shell

FROM alpine:3.20
COPY <<EOF /usr/local/bin/entrypoint
#!/usr/bin/env bash
set -eu; mkdir -p /run/app /var/cache/app; chown -R app:app /run/app /var/cache/app
EOF

Good: COPY shell

FROM alpine:3.20
COPY <<EOF /usr/local/bin/entrypoint
#!/usr/bin/env bash
set -eu
mkdir -p /run/app /var/cache/app
chown -R app:app /run/app /var/cache/app
EOF

Bad: RUN PowerShell

FROM mcr.microsoft.com/powershell:lts-alpine-3.20
SHELL ["pwsh", "-Command"]
RUN <<EOF
if ($true) {
Write-Host hi
}
EOF

Good: RUN PowerShell

FROM mcr.microsoft.com/powershell:lts-alpine-3.20
SHELL ["pwsh", "-Command"]
RUN <<EOF
if ($true) {
    Write-Host hi
}
EOF

Supported Types

ExtensionFormat
.jsonJSON
.yaml, .ymlYAML
.tomlTOML
.xml, .config, .xsd, .wsdl, .xsl, .xsltXML
.iniINI

Supported PowerShell Targets

ExtensionFormat
.ps1PowerShell script
.psm1PowerShell script module
.psd1PowerShell data file or module manifest

Supported Shells

Shell formatting applies to executable RUN heredocs and to COPY heredocs with a recognized shell shebang. A COPY heredoc targeting .sh without a shebang uses POSIX shell. PowerShell RUN heredocs use PSScriptAnalyzer instead of a shfmt dialect.
ShellFormatter dialect
bashBash
sh, dash, ashPOSIX shell
mksh, kshmksh
batsBats
zshzsh

Configuration

Default (no config needed):
# Enabled by default with no rule-specific options
This rule intentionally has no rule-specific formatter options. Use .editorconfig for indentation style and width, YAML scalar wrapping width, and POSIX-style shell heredoc line width. See EditorConfig integration for recommended Dockerfile settings and path resolution details. The rule reads indent_style, indent_size, tab_width, and max_line_length. For YAML, max_line_length is used as a preferred scalar wrapping width. For shell heredocs, it is used as a preferred command wrapping width; when unset or invalid, tally uses 100, and off disables this shell line-width pass. This is not a hard maximum. For POSIX-style shell heredocs, tally also reads shfmt-compatible EditorConfig booleans: binary_next_line, switch_case_indent, space_redirects, keep_padding, function_next_line, simplify, and minify. The rule does not currently interpret insert_final_newline, end_of_line, charset, or trim_trailing_whitespace for the virtual payload file. Formatted heredoc bodies always end with exactly one newline before the heredoc terminator, and line endings follow the parent Dockerfile/fix application rather than a separate payload setting. The current formatters do not emit incidental trailing whitespace, but tally does not run a separate trailing-whitespace trim pass because whitespace can be payload data.

Auto-fix

Run:
tally lint --fix Dockerfile
To format heredocs produced by another selected rule, select both rules:
tally lint --fix --fix-unsafe \
  --select tally/prefer-copy-heredoc \
  --select tally/prefer-formatted-heredocs \
  Dockerfile