Intermediate

Dry-Run and Preview Patterns

The single most important safety rule for AI agents interacting with infrastructure: always preview before applying. This lesson covers dry-run flags, preview commands, and wrapper scripts that enforce this pattern across every tool.

The Golden Rule: Plan Before Apply

Every major infrastructure tool has a "preview" or "dry-run" mode that shows what would happen without actually making changes. When AI agents work with infrastructure, these preview commands should always run first:

ToolPreview CommandApply CommandKey Flag
Terraformterraform planterraform apply-out=plan.tfplan
Pulumipulumi previewpulumi up--diff
Kuberneteskubectl diffkubectl apply--dry-run=server
Helmhelm template / helm diffhelm upgrade--dry-run
Azure CLIaz deployment what-ifaz deployment create--what-if
AWS CDKcdk diffcdk deployN/A
CloudFormationaws cloudformation create-change-setaws cloudformation execute-change-setChange sets
Ansibleansible-playbook --checkansible-playbook--check --diff

Terraform Plan: The Gold Standard

Terraform's plan/apply workflow is the model all agent-infrastructure interactions should follow:

Bash - Safe Terraform Workflow for Agents
#!/bin/bash
# STEP 1: Always plan first and save the plan file
terraform plan -out=agent-plan.tfplan

# STEP 2: Review the plan output for destructive changes
# Look for: "destroy", "replace", "forces replacement"
terraform show agent-plan.tfplan

# STEP 3: Only if the plan looks safe, apply the saved plan
# Using the saved plan ensures exactly what was reviewed gets applied
terraform apply agent-plan.tfplan

# NEVER do this - it skips the review step:
# terraform apply -auto-approve  ← DANGEROUS!
Critical: Never let an AI agent run terraform apply -auto-approve. This skips the plan review entirely. Always use the two-step plan-then-apply workflow with a saved plan file.

Kubernetes --dry-run Modes

Kubernetes offers two dry-run modes, and the distinction matters for safety:

Bash - Kubernetes Dry-Run Modes
# Client-side dry-run: validates YAML locally only
# Does NOT check against the cluster (less accurate)
kubectl apply -f deployment.yaml --dry-run=client -o yaml

# Server-side dry-run: sends to API server for validation
# Checks admission webhooks, schema, etc. (more accurate)
kubectl apply -f deployment.yaml --dry-run=server -o yaml

# kubectl diff: shows what would change (BEST for review)
# Compares your YAML against the live cluster state
kubectl diff -f deployment.yaml

# Safe agent workflow:
# 1. Run kubectl diff first
# 2. Review the diff output
# 3. Only then run kubectl apply

Azure What-If Deployments

Azure Resource Manager provides a what-if operation that previews changes before deploying:

Bash - Azure What-If Deployment
# Preview what an ARM/Bicep deployment would change
az deployment group what-if \
  --resource-group myResourceGroup \
  --template-file main.bicep \
  --parameters @parameters.json

# Output shows color-coded changes:
# + Create (green)  - new resources
# ~ Modify (purple) - changed properties
# - Delete (red)    - resources to be removed
# = NoChange        - unchanged resources

# For subscription-level deployments:
az deployment sub what-if \
  --location eastus \
  --template-file main.bicep

Building Dry-Run into Agent Workflows

The key challenge is making sure agents always run the preview step, not just sometimes. Here's how to enforce this:

  1. CLAUDE.md / Agent Config Rules

    Add explicit rules: "ALWAYS run terraform plan before terraform apply" and "ALWAYS run kubectl diff before kubectl apply."

  2. Shell Wrapper Scripts

    Replace the real CLI tools with wrapper scripts that enforce the plan-first pattern automatically.

  3. Pre-execution Hooks

    Use Claude Code hooks or custom middleware to intercept apply commands and inject a plan step.

  4. CI/CD Enforcement

    Move all apply operations to CI/CD pipelines where plan-before-apply is structurally enforced.

Parsing Plan Output to Detect Destructive Changes

After running a plan/preview, you need to programmatically check for dangerous changes. Here's a Python script that parses Terraform plan output:

Python - Terraform Plan Safety Checker
import json
import subprocess
import sys

def check_terraform_plan(plan_file):
    """Parse a Terraform plan and flag destructive changes."""

    # Convert plan to JSON for parsing
    result = subprocess.run(
        ["terraform", "show", "-json", plan_file],
        capture_output=True, text=True
    )
    plan = json.loads(result.stdout)

    destroys = []
    replaces = []
    creates = []

    for change in plan.get("resource_changes", []):
        actions = change.get("change", {}).get("actions", [])
        resource = f"{change['type']}.{change['name']}"

        if "delete" in actions:
            destroys.append(resource)
        if "create" in actions and "delete" in actions:
            replaces.append(resource)
        if actions == ["create"]:
            creates.append(resource)

    # Report findings
    print(f"Plan Summary:")
    print(f"  Creates:  {len(creates)}")
    print(f"  Replaces: {len(replaces)}")
    print(f"  Destroys: {len(destroys)}")

    if destroys:
        print(f"\n⛔ DESTRUCTIVE CHANGES DETECTED:")
        for r in destroys:
            print(f"  - {r} will be DESTROYED")
        print("\nBlocking apply. Review required.")
        sys.exit(1)

    if replaces:
        print(f"\n⚠️ REPLACEMENT CHANGES (destroy + recreate):")
        for r in replaces:
            print(f"  - {r} will be REPLACED")
        print("\nManual approval required.")
        sys.exit(2)

    print("\n✅ Plan looks safe. No destructive changes.")
    sys.exit(0)

if __name__ == "__main__":
    check_terraform_plan(sys.argv[1])

Wrapper Scripts That Enforce Plan-Before-Apply

Replace the actual CLI commands with wrapper scripts that enforce the preview step. Place these wrappers earlier in $PATH than the real tools:

Bash - Safe Terraform Wrapper
#!/bin/bash
# /usr/local/bin/safe-terraform
# Wrapper that prevents terraform apply without a saved plan

REAL_TERRAFORM="/usr/bin/terraform"

if [[ "$1" == "apply" ]]; then
    # Check if a plan file was provided
    if [[ "$2" != *.tfplan ]]; then
        echo "❌ BLOCKED: terraform apply requires a saved plan file."
        echo "   Run: terraform plan -out=plan.tfplan"
        echo "   Then: terraform apply plan.tfplan"
        exit 1
    fi

    # Check for -auto-approve flag
    if echo "$@" | grep -q "auto-approve"; then
        echo "❌ BLOCKED: -auto-approve is not allowed."
        echo "   Use a saved plan file instead."
        exit 1
    fi
fi

if [[ "$1" == "destroy" ]]; then
    echo "❌ BLOCKED: terraform destroy is not allowed via CLI."
    echo "   Use the CI/CD pipeline for destroy operations."
    exit 1
fi

# Pass through all other commands
$REAL_TERRAFORM "$@"
Bash - Safe kubectl Wrapper
#!/bin/bash
# /usr/local/bin/safe-kubectl
# Wrapper that enforces diff-before-apply for kubectl

REAL_KUBECTL="/usr/bin/kubectl"

if [[ "$1" == "delete" ]] && [[ "$2" == "namespace" ]]; then
    echo "❌ BLOCKED: kubectl delete namespace is not allowed."
    echo "   Use the CI/CD pipeline for namespace deletion."
    exit 1
fi

if [[ "$1" == "apply" ]]; then
    echo "Running kubectl diff first..."
    $REAL_KUBECTL diff "${@:2}"
    DIFF_EXIT=$?

    if [[ $DIFF_EXIT -eq 0 ]]; then
        echo "No changes to apply."
        exit 0
    elif [[ $DIFF_EXIT -eq 1 ]]; then
        echo ""
        read -p "Apply these changes? (y/N): " confirm
        if [[ "$confirm" == "y" ]]; then
            $REAL_KUBECTL apply "${@:2}"
        else
            echo "Apply cancelled."
            exit 0
        fi
    else
        echo "❌ Diff failed. Not proceeding with apply."
        exit 1
    fi
else
    $REAL_KUBECTL "$@"
fi
Pro tip: Store these wrapper scripts in your project's bin/ directory and add it to $PATH at the start of each agent session. This way, every terraform or kubectl command the agent runs goes through your safety layer automatically.

Dry-Run Flags Across CLI Tools

A comprehensive reference of dry-run flags across common CLI tools that AI agents might use:

ToolDry-Run FlagWhat It Does
rsync--dry-run or -nShows what files would be transferred
rm-i (interactive)Prompts before each removal
docker compose--dry-runShows what commands would execute
npm--dry-runShows what would be published/installed
pip--dry-runShows what would be installed
aws s3 rm--dryrunShows what objects would be deleted
gcloud--dry-run (limited)Validates without executing
make-nShows commands without executing

Key Takeaways

  • Always preview infrastructure changes before applying — no exceptions
  • Use saved plan files (e.g., terraform plan -out=plan.tfplan) to ensure what was reviewed is exactly what gets applied
  • Enforce plan-before-apply with wrapper scripts placed in the agent's $PATH
  • Parse plan output programmatically to auto-detect and block destructive changes
  • Block -auto-approve, destroy, and delete namespace commands at the wrapper level
  • Move apply operations to CI/CD pipelines where plans are structurally required