Plan Review and Policy as Code
terraform plan produces a human-readable and machine-readable preview of every action Terraform intends to take: resources to create, update in place, or destroy. Reviewing that plan — either by a human reading the diff in a pull request, or by an automated policy engine — is the last checkpoint before infrastructure actually changes. As teams scale, manual review alone doesn't catch every risky change, so organizations layer policy-as-code on top: machine-enforced rules that inspect the plan (or the resulting plan JSON) and block an apply if it violates a defined guardrail, such as 'never allow a public S3 bucket' or 'require a specific tag on every resource'.
Cricket analogy: terraform plan is like a third umpire's review screen showing exactly what the decision will change before it's finalized; policy-as-code is like the DRS protocol automatically overturning any review that violates a fixed rule, no human debate needed.
Reading a plan diff
Plan output uses symbols to summarize intent: a + prefix means create, - means destroy, ~ means update in place, and -/+ means the resource must be destroyed and recreated because a change requires replacement (for example, changing an EC2 instance's AMI in some configurations). The summary line at the end — 'Plan: 2 to add, 1 to change, 1 to destroy' — is the fastest signal for reviewers, but the detail above it is what actually needs scrutiny, especially any unexpected -/+ replacement of a stateful resource like a database.
Cricket analogy: Plan symbols like + and -/+ are like scorecard notations: a run scored is +, a wicket is -, and a batter retired hurt then returning to bat again is -/+, requiring scrutiny beyond just the final score summary.
Terraform will perform the following actions:
# aws_db_instance.primary must be replaced
-/+ resource "aws_db_instance" "primary" {
~ engine_version = "14.9" -> "15.4" # forces replacement
identifier = "prod-primary-db"
}
# aws_s3_bucket_policy.public will be created
+ resource "aws_s3_bucket_policy" "public" {
+ bucket = "acme-reports"
+ policy = jsonencode({...})
}
Plan: 1 to add, 0 to change, 0 to destroy, 1 to replace.Policy as code with Sentinel and OPA
HashiCorp's Sentinel (used in Terraform Cloud/Enterprise) and Open Policy Agent's Rego (used via conftest or custom tooling) both let teams write rules against the structured plan JSON output (terraform show -json). Policies typically run in one of three enforcement levels: advisory (warn only), soft-mandatory (can be overridden by an authorized approver), or hard-mandatory (blocks the apply unconditionally). A common policy set enforces tagging standards, restricts instance types to an approved list, forbids 0.0.0.0/0 ingress rules on security groups, and requires encryption on storage resources.
Cricket analogy: Sentinel's advisory, soft-mandatory, and hard-mandatory levels are like an umpire's warning, a captain's review override, and an automatic no-ball call — three escalating tiers of enforcement against a fixed set of match rules.
import "tfplan/v2" as tfplan
mandatory_tags = ["Owner", "Environment", "CostCenter"]
required_tags_present = rule {
all tfplan.resource_changes as _, rc {
rc.type is not "aws_instance" or
all mandatory_tags as tag {
rc.change.after.tags contains tag
}
}
}
main = rule {
required_tags_present
}Policy as code turns tribal knowledge ('we always require an Owner tag') into an executable, testable artifact that runs identically for every engineer and every pipeline run, rather than relying on a human reviewer to remember and catch it.
A -/+ (destroy-and-recreate) on a stateful resource like a database or a persistent volume is one of the most dangerous lines in any plan — always confirm whether create_before_destroy or a moved block could avoid data loss before approving it.
- Plan symbols
+,-,~, and-/+indicate create, destroy, in-place update, and destroy-then-recreate respectively. - The plan summary line is a quick signal, but replacement actions on stateful resources need close manual scrutiny.
- Policy as code (Sentinel, OPA/Rego) evaluates the structured plan JSON against organizational rules automatically.
- Enforcement levels range from advisory (warn) to soft-mandatory (overridable) to hard-mandatory (blocking).
- Common policies restrict public network access, enforce mandatory tags, and require encryption on storage resources.
- Policy as code makes governance consistent and auditable instead of dependent on manual reviewer memory.
Practice what you learned
1. What does a `-/+` prefix in `terraform plan` output indicate?
2. What input do policy-as-code tools like Sentinel and OPA typically evaluate?
3. In Sentinel policy enforcement levels, what does 'soft-mandatory' mean?
4. Why does a `-/+` replacement on a production database deserve special reviewer attention?
5. Which is a typical rule enforced by a policy-as-code guardrail?
Was this page helpful?
You May Also Like
terraform init, plan, and apply
The three-command core workflow of Terraform: init downloads dependencies, plan previews changes, and apply executes them -- forming a safe, repeatable provisioning cycle.
Terraform in CI/CD Pipelines
Learn the standard stages, safety controls, and credential-handling patterns for running Terraform reliably inside a CI/CD pipeline, from plan-on-PR to gated apply-on-merge.
Terraform Cloud and Terraform Enterprise
Understand HashiCorp's managed and self-hosted platforms for running Terraform collaboratively, including remote runs, workspaces, and policy enforcement.
Drift Detection
Understand configuration drift — when real infrastructure diverges from Terraform state — and the tools and practices used to detect and reconcile it before it causes surprises.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics