BLOG/CONFIGURATION MANAGEMENT
THEME
CONFIGURATION MANAGEMENT

Wrap Terraform with guardrails

fmt, validate, plan, approve, apply — in that order, every time. Wrap your Terraform commands in a workflow so the safety steps aren't optional and the apply is gated behind a confirm and a dry-run.

May 8, 2026 OrchStep Team 7 minROLE: Platform EngineerSCALE: Any
RUNNABLE DEMO
Full source for this post: blog/terraform-guardrails
VIEW SOURCE

Everyone knows the safe Terraform sequence: fmt, validate, plan, look at the plan, then apply. Everyone also knows the 11pm version where someone runs terraform apply straight, skips the plan review, and learns something new about their state file.

The sequence isn't hard. Keeping it non-optional is. A workflow makes the safe order the only order — and puts a human confirm and a dry-run between you and a production apply.

The pipeline as tasks

Each Terraform command is a task. plan calls fmt and validate first, so you physically cannot plan without formatting and validating. apply calls plan, then gates the real apply behind a prompt.

orchstep.yml
name: terraform
defaults:
  stage: staging
  workdir: infra

tasks:
  fmt:
    steps:
      - name: fmt
        func: shell
        do: echo "terraform -chdir={{ vars.workdir }} fmt -check -recursive"

  validate:
    steps:
      - name: init
        func: shell
        do: echo "terraform -chdir={{ vars.workdir }} init -backend=false"
      - name: validate
        func: shell
        do: echo "terraform -chdir={{ vars.workdir }} validate"

  # orchstep run plan --var stage=production
  plan:
    steps:
      - name: fmt
        task: fmt
      - name: validate
        task: validate
      - name: plan
        func: shell
        do: echo "terraform -chdir={{ vars.workdir }} plan -out=tfplan ({{ vars.stage }})"

  # orchstep run apply --var stage=production --dry-run
  apply:
    desc: "Plan, ask for approval, then apply"
    steps:
      - name: plan
        task: plan
      - name: approve
        func: prompt
        args:
          message: "Apply this plan to {{ vars.stage }}?"
          type: confirm
          default: false
      - name: gate
        if: '{{ eq steps.approve.value "true" }}'
        then:
          - name: apply
            func: shell
            do: echo "terraform -chdir={{ vars.workdir }} apply tfplan"
        else:
          - name: hold
            func: shell
            do: echo "apply to {{ vars.stage }} not approved; nothing changed"

The tasks compose. apply -> plan -> fmt + validate is wired by task: references, so the guardrails run in order whether you start at plan or at apply. The confirm prompt defaults to false: in CI, where nobody is at the keyboard, the gate takes the else: branch and refuses to apply rather than silently shipping.

The two-command habit

orchstep run apply --var stage=production --dry-run

Dry-run first, every time. It resolves the variables, walks the composed tasks, and shows you exactly which steps would run — including that the apply is the prompt-gated branch — without executing anything.

When the plan looks right, drop the flag:

orchstep run apply --var stage=production

Now it runs fmt, validate, plan, and stops at the confirm. You type y or you don't. There's no path where the apply happens because you forgot a step.

What you gained

ConcernBare terraformOrchStep wrapper
Run orderdisciplineenforced by task: composition
Skip the planone command awayimpossible — apply calls plan
Apply guardnoneprompt confirm, defaults to no
CI safetyapplies if invokedelse: branch holds
Previewterraform plan--dry-run over the whole pipeline

Where this is too much

If you already run Terraform through Atlantis, Terraform Cloud, or a Spacelift pipeline, that is your guardrail layer — don't double-wrap it. This pattern is for the team running Terraform from laptops and a thin CI job, who want the safe sequence to be one command instead of a wiki page nobody reads.

Where to go next

The project above is a runnable, echo-only demo. Swap the echos for real terraform commands once the order is wired the way you want.

#TERRAFORM#INFRASTRUCTURE#GUARDRAILS#APPROVAL
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — CONFIGURATION MANAGEMENT