Skip to content

GitHub Actions Heredoc Indentation Guide

When writing heredocs that generate YAML files in GitHub Actions workflows (like pr-preview.yml), you must understand how whitespace is processed.

How Whitespace Processing Works

  1. YAML Block Scalar Processing: GitHub Actions parses the workflow YAML first. Content in a run: | block has its base indentation stripped (~10 spaces for steps nested under jobs).

  2. sed Post-Processing: The workflow uses sed to strip any remaining leading spaces.

Two Valid Patterns

Both patterns produce correct output - choose either and be consistent:

Pattern A: 10 spaces base (simpler)

            cat > file.yaml <<EOF
          apiVersion: v1          # 10 spaces → 0 after YAML
          spec:
            containers:           # 12 spaces → 2 after YAML
          EOF
            sed -i 's/^          //' file.yaml  # sed matches nothing (no-op), but output is already correct
  • Used by: pr.yaml, namespace.yaml, mongodb-user.yaml
  • The sed command is effectively a no-op (could be removed)

Pattern B: 20 spaces base

            cat > file.yaml <<EOF
                    apiVersion: v1          # 20 spaces → 10 after YAML → 0 after sed
                    spec:
                      containers:           # 22 spaces → 12 after YAML → 2 after sed
          EOF
            sed -i 's/^          //' file.yaml  # sed strips 10 spaces
  • Used by: db-reset-job.yaml
  • The sed command actively strips remaining indentation

The Critical Rule

The sed command must match what remains AFTER YAML processing:

  • If content has 10 spaces base → after YAML has 0 spaces → sed (10 spaces) = no-op
  • If content has 20 spaces base → after YAML has 10 spaces → sed (10 spaces) = strips correctly
  • If content has 20 spaces base → after YAML has 10 spaces → sed (20 spaces) = BUG!

The Bug That Breaks Things

# WRONG - sed doesn't match what YAML processing left behind
            cat > file.yaml <<EOF
                    apiVersion: v1          # 20 spaces → 10 after YAML
                    spec:
                      containers:           # 22 spaces → 12 after YAML
                        image: bar          # 26 spaces → 16 after YAML
          EOF
            sed -i 's/^                    //' file.yaml  # Tries to strip 20 spaces!

Result: apiVersion (10 spaces) doesn't match sed → stays at column 10. image (16 spaces) doesn't match → stays at column 16. Only lines with exactly 20+ spaces get stripped to 0, producing broken YAML.

Files in pr-preview.yml

File Pattern Base Spaces sed Strips
pr.yaml A 10 10 (no-op)
namespace.yaml A 10 10 (no-op)
mongodb-user.yaml A 10 10 (no-op)
db-reset-job.yaml B 20 10