TerraformCLI icon
Docker icon
MailSend icon
Pause icon
Log icon
Webhook icon

Terraform Plan and Apply with an Approval Gate

Webhook-triggered Terraform pipeline that plans, emails a reviewer, pauses for approval, and applies the exact saved plan with full audit trail.

Categories
Infrastructure

Enforce human review before infrastructure changes land. This Kestra blueprint turns Terraform plan and apply into a governed, auditable pipeline: a webhook kicks off the run, terraform plan produces a saved tfplan artifact, a reviewer is emailed, the execution pauses for explicit approval, and terraform apply then runs against the exact reviewed plan so what ships is what was approved. It closes the classic gap between CI driven Terraform and ad hoc local applies, giving platform and SRE teams a single, declarative approval gate with full execution lineage.

How it works

  1. The on_apply_request trigger (io.kestra.plugin.core.trigger.Webhook) accepts a POST from a change management system, chat bot, or ITSM tool, secured by TERRAFORM_APPLY_WEBHOOK_KEY.
  2. The plan task (io.kestra.plugin.terraform.cli.TerraformCLI) runs terraform init then terraform plan -no-color -out=tfplan inside the hashicorp/terraform:latest container via the Docker task runner. It uses an S3 remote backend so state is shared between containers, and exports tfplan as an output file.
  3. The notify_approval task (io.kestra.plugin.email.MailSend) emails the on-call reviewer with the execution id and a link target.
  4. The approve task (io.kestra.plugin.core.flow.Pause) holds the execution for up to PT2H waiting for a human to resume it from the Kestra UI or API.
  5. The apply task replays the saved tfplan via terraform apply -no-color -auto-approve tfplan, guaranteeing zero drift from what was reviewed.
  6. The log_result task (io.kestra.plugin.core.log.Log) records completion for the audit trail.

What you get

  • A reviewed-then-applied workflow where the artifact applied is bit-for-bit the artifact reviewed.
  • Webhook entry point that fits ServiceNow, Jira, GitHub, Slack approvals, or any HTTP caller.
  • Email notification to whoever owns the change window.
  • Full execution history, logs, and outputs in the Kestra UI for compliance.
  • Container-isolated Terraform runs, no shared state on developer laptops.

Who it's for

  • Platform and DevOps engineers who need a change-management gate on Terraform.
  • SRE teams running production infra where unreviewed applies are unacceptable.
  • Regulated organizations that must prove who approved what, when.

Why orchestrate this with Kestra

Terraform itself has no scheduler, no human-in-the-loop primitive, and no native way to bind a plan artifact to a later approved apply. Kestra adds the missing layer: an event-driven webhook trigger, a first-class Pause task for human approval, automatic retries on transient failures, persisted inputs and output files that link plan to apply, and declarative YAML you can version control next to your Terraform code. You get lineage across the whole change, not just the CLI output of a single command.

Prerequisites

  • An S3 bucket (or other remote backend) configured in the Terraform backend block. Remote state is required because plan and apply run in separate containers.
  • SMTP relay reachable from the Kestra worker.
  • A caller that can POST to the webhook with the configured key.

Secrets

  • AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY: credentials for the S3 remote backend and any AWS provider resources.
  • TERRAFORM_APPLY_WEBHOOK_KEY: shared secret guarding the Webhook trigger.
  • SMTP_HOST, SMTP_USERNAME, SMTP_PASSWORD: SMTP relay used by MailSend to notify the reviewer.

Quick start

  1. Add the secrets above to your Kestra namespace.
  2. Replace the stub main.tf and backend block with your real Terraform configuration.
  3. Adjust the from, to, and subject of the notification email to your team.
  4. Deploy the flow and POST to the webhook URL with the configured key, or trigger a manual execution.
  5. Open the paused execution in the Kestra UI, review the plan output, and resume to apply.

How to extend

  • Swap S3 for GCS, Azure Blob, or Terraform Cloud by changing the backend block and provider env vars.
  • Replace MailSend with io.kestra.plugin.notifications.slack.SlackIncomingWebhook or a ServiceNow change ticket update.
  • Add a Switch task after plan to auto-approve no-op plans and only pause when there are real changes.
  • Post the plan diff into the approval message by parsing tfplan with terraform show -json.
  • Add a tfsec or checkov task between plan and notify_approval to block applies on policy violations.
  • Wire a second webhook to call terraform destroy through the same approval gate.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.