Hey {{first name | there}}. Terraform has no built-in scheduler, and the first time you hit that wall, it reads like a missing feature.
It is not. It is the boundary that keeps Terraform declarative, and the moment you bolt an imperative scheduler onto a declarative provisioning tool.
You start collecting state lock fights and apply runs nobody can account for.
So the real question is how you schedule and orchestrate around Terraform without breaking that.
Here is one answer, written as a single flow:
id: terraform-approval-plan-apply
namespace: company.team
description: |
Put a human gate between terraform plan and apply. A webhook starts the run,
the flow plans and shows the changes, pauses for approval, then applies only
the reviewed plan and logs the result.
tasks:
- id: plan
type: io.kestra.plugin.terraform.cli.TerraformCLI
description: Produce a saved plan for review.
taskRunner:
type: io.kestra.plugin.scripts.runner.docker.Docker
containerImage: hashicorp/terraform:latest
env:
AWS_ACCESS_KEY_ID: "{ secret('AWS_ACCESS_KEY_ID') }"
AWS_SECRET_ACCESS_KEY: "{ secret('AWS_SECRET_ACCESS_KEY') }"
inputFiles:
main. tf: |
See the full YAML configuration here.
📰 TECHNICAL NOTES: Why Terraform’s missing scheduler is the point
It comes down to declarative versus imperative. Terraform is declarative: you define the end state, what infrastructure should exist, and let it work out the steps.
Schedulers and scripts are imperative: you define the step-by-step actions and the timing, when and how things happen.
Both are fine on their own.
The trouble starts when you push the imperative half into the declarative tool.
Cron wrapped around terraform apply -auto-approve, a CI job that reruns plan on a timer, a bash wrapper only one person fully understands.
You end up with applies firing on a schedule while nobody is watching, and a state file that several jobs think they own.
It works right up until two of them run at once.
Scheduling without going imperative
The fix is not to make Terraform imperative. It is to orchestrate it from a layer that is also declarative.
Kestra is declarative from day one, so instead of writing an imperative code layer just to schedule a trigger, the tasks, their dependencies, and the schedule all live in one YAML file.
The flow above is the whole pattern.
A webhook triggers it, plan runs, the flow parks on the review pause and emails a named reviewer, and “apply” only moves once they resume it.
Because apply runs the saved tfplan, the diff that ships is the exact one that got approved, not a fresh plan that drifted in the thirty minutes since.
The approver and the timestamp get written into the run history on their own, so the review is part of the run instead of a Slack thread sitting next to it.

Terraform Plan and Apply with an Approval Gate Flow
What starts to matter with a team on it
On a solo project, none of this matters. It starts to matter the moment the flow is shared, and it is mostly what keeps the approval gate above from turning into a checkbox
RBAC decides who can run, edit, and approve a flow, scoped per namespace.
Assets keep a live inventory of the resources your flows touch, VMs, IPs, certs, tables, with lineage and an audit trail.
Task Runners push the execution itself onto isolated runners on Kubernetes or cloud batch, on demand, air-gapped included.
🌍IN THE ECOSYSTEM
Terraform Plan and Apply with an Approval Gate (Kestra Blueprint): the flow above, ready to clone and point at your own state. Fastest way to watch the pause block and apply. Clone it to try it now.
Declarative from day one (Kestra blog): the reasoning behind orchestrating in YAML rather than bolting on an imperative layer. Useful if you are weighing this against a scheduler you already run.
Kestra is Open Source with 27k+ stars: Open source and self-hosted, so if you would rather read the code than the Blueprint, start at the repo. Worth a star while you are there.
⏱️UNTIL NEXT TIME
I have known and explored Kestra for a year now, but this is my first time writing about it publicly.
What kept coming up, in my own use and from people running it in production, is boring in the way good infrastructure is boring.
The flow is a YAML file, so it reviews like any other pull request, and someone who does not write the underlying scripts can still read it and approve it.
Know an engineer wrestling with this? Share this link with them
Thank you to Kestra for Sponsoring Today’s Newsletter!
Divine Odazie
CTO, EverythingDevOps


