Plan and apply¶
The lifecycle¶
- Open a pull request touching one or more units. The engine plans every impacted unit automatically, one job per unit, in parallel. Only apply is dependency-ordered; a plan reads no other unit's output, so nothing has to wait.
- Read the plan comment. Each unit posts a fresh comment led by a validation check, then the plan output, a change summary, apply instructions, and the gate findings.
- Get the required reviews.
/applyrespects the repo's branch protection. - Apply. Comment
/applyto apply everything in the PR, or/apply -p <label>for one unit. Units apply in dependency order, one at a time. - Merge. Once applied, the
tf-pr-ops / merge-gatecheck turns green and the PR can merge.
Commands¶
| Comment | Effect |
|---|---|
/plan |
Re-plan all impacted units. |
/plan -p <label> |
Plan one unit. <label> is the unit path, its name, or its directory. |
/apply |
Apply all impacted units, in dependency order. |
/apply -p <label> |
Apply one unit. |
/unlock |
Force-release the unit locks this PR holds. |
digger plan / digger apply / digger unlock |
Accepted aliases, for muscle memory. |
Opening, updating, or reopening a PR runs /plan automatically. A plan with
changes includes an instructions block telling you exactly which /apply to run.
The impacted set¶
"Impacted" is wider than "has a plan diff", and wider than "the files you edited". A unit is impacted when any of three things is true:
- its own directory changed,
- one of the files it reads changed, or
- it is downstream of an impacted unit through a
dependenciesblock.
The read-files rule is the surprising one. Terragrunt reports every file a unit
pulls in, which includes root.hcl, tenant.hcl, and the subscription.hcl and
region.hcl above it. A one-line change to live/root.hcl therefore impacts
every unit in the tree, because every unit reads it. Expect the full fan-out, and
expect most of those plans to come back as no-ops.
That is the intent. A shared contract change is planned everywhere it lands, not only where someone remembered to look.
Cross-PR unit locks¶
The first pull request to plan a unit owns it until that pull request merges or
closes. A second PR touching the same unit does not plan it: the unit reports a
locked result instead, with a comment naming the owning PR, a failing per-unit
check, and a failing merge gate reading Unit locked by another PR. This stops
a stale plan in one PR from silently reverting what another PR applied.
Locks release three ways:
- Merging or closing the owning PR. This is the normal path; the caller
workflow's
closedtrigger is what makes it fire. - Commenting
/unlockon the owning PR force-releases its locks, for when a PR is parked but should not block others. The engine confirms with a comment. - Re-running the blocked PR's plan after the lock is gone acquires it.
Ownership lives in an Azure Storage Table (tfprlocks) on the state storage
account, one row per unit; the bootstrap grants the identities access. Locking
skips itself on drift sweeps and manual dispatches, which never own units. A
repository can point locks at a different account with the
TFPR_LOCK_STORAGE_ACCOUNT variable, or turn the feature off with
TF_PR_OPS_DISABLE_LOCKS set to true. See
Secrets and variables.
The plan comment¶
Each run posts a new comment, so the PR keeps a chronological history of runs. A comment is ordered for scanning:
- A validation check first, expanded: the pass or fail headline, so a plan error shows at the top instead of below a wall of output.
- Pre-plan checks, if any pre-plan hooks produced output.
- Plan output in a collapsible block.
- A plan summary table of what will change.
- Instructions with the exact
/applycommands, expanded, when the plan has changes. - Additional output from the
post_plangates: policy, security, and cost. This panel is status-aware: collapsed with a check mark when every gate is clean, and auto-expanded with a warning when a gate reports a finding or the run failed, so a policy, security, or cost finding is never hidden.
Comment reactions¶
The engine reacts to your /plan or /apply comment so you can see progress at a
glance without opening the run:
- 👀 the command was seen and is being resolved,
- 🚀 the plan or apply is running,
- 🎉 it succeeded, or 👎 it failed (the 🚀 is removed when it finishes).
The running and final states also appear as a native check run (a spinner, then a green check or red cross) in the PR's Checks section.
The command gate¶
Before any of that, the engine checks who typed the command. /plan and /apply
are accepted only from a comment whose author_association is OWNER, MEMBER,
or COLLABORATOR. Anyone else gets a 😕 reaction and a reply saying the command
was refused. This is not a write-permission check: MEMBER means organisation
member, which can include people with read-only access. See
Who can run a command.
The approval gate¶
/apply is blocked until the PR meets the repo's required reviews. When it is not
yet approved, the engine posts a comment explaining that apply is blocked, and the
apply job does not run. This uses GitHub's own review decision, so it is configured
entirely in branch protection or rulesets.
A repository that requires no approving reviews at all is a special case: GitHub
reports no review decision, and the engine refuses that too. Such a repository
opts back in with the TF_PR_OPS_ALLOW_UNREVIEWED_APPLY variable. See
Secrets and variables.
Dependency-ordered apply¶
Apply is a single job, not a parallel matrix, so Terragrunt units apply after the units they depend on. If a dependency fails, its dependents are skipped rather than applied against stale state, and the skipped units are reported.
The merge gate¶
The tf-pr-ops / merge-gate status check is the safety net. It is green
when:
- the PR has no Terraform changes, or
- every unit is a no-op, or
- every impacted unit has been applied.
It is red while changes remain unapplied, or when a plan or apply failed. It also blocks when the PR changed Terraform but discovery selected no units, so an unapplied net-new unit or a removed unit cannot slip through green. Made a required check, it guarantees the main branch always matches applied infrastructure.