A common trap I see in teams using Infrastructure as Code is leaving the terraform plan output to a human reviewer and nothing else. Code review is valuable; but checking, by hand and on every PR, things like tagging standards, internet-exposed resources, region selection, cost limits, or encryption requirements isn’t sustainable. An OPA-based pipeline turns the plan output into a policy decision and routes changes through an extra safety layer beyond human judgment.

Core flow
The goal is to put Terraform code through two gates before apply:
- Generate the upcoming change with
terraform plan - Evaluate the plan JSON output against the OPA policy set
This way, the question “what will happen in the cloud account?” stays separate from “is this change allowed?” — but they meet inside the same pipeline.
Producing the plan output as JSON
The first step is standard:
terraform init
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
OPA or Conftest typically operates against tfplan.json. The important point is that the policy engine looks at the actual plan result, not at the HCL file. Because module expansion, variable resolution, and provider behavior only become concrete at the plan step.
Sample policy scenarios
High-value rules to start with include:
- Reject production resources that would receive a public IP
- Fail the plan when required tags are missing
- Block storage resources without encryption
- Reject resources opened outside specific regions
- Emit a warning for high-cost instance types
A simple Rego sample:
package terraform.guardrails
deny[msg] {
some rc in input.resource_changes
rc.type == "aws_s3_bucket"
after := rc.change.after
after.tags.environment == "production"
not after.server_side_encryption_configuration
msg := sprintf("production bucket encryption missing: %s", [rc.address])
}
This rule alone isn’t enough, but it shows the approach clearly: resource change, target state, and business context are evaluated together.
Pipeline design
A CI flow can typically be structured in this order:
- Format and validate
- Plan generation
- Plan JSON extraction
- OPA or Conftest policy evaluation
- Writing the results back to the PR comment
In this flow, the policy result shouldn’t be a simple “pass/fail.” Which resource hit which rule should be visible directly on the PR. Otherwise the developer’s fix loop drags out.
Managing the policy set
The reason most policy projects fail isn’t technical — it’s governance. These need to be clarified:
- Is the policy repository separate from the application code?
- How will versioning work?
- Is there an emergency-exception mechanism?
- How are warning rules separated from blocking rules?
Making every rule blocking from day one provokes pushback in teams. The healthy model is to deliver visibility first, then turn the critical rules into a gate.
Extra controls for enterprise use
In enterprises, policies are produced not only by security but also by platform and finance teams. So splitting rules into three groups helps:
- Security guardrails
- Operational and observability requirements
- Cost and governance rules
For example, you can require a log-collection agent or a monitoring tag on every production resource. That way, IaC validation also carries the observability standard along with it.
Conclusion
Routing Terraform plan output through an OPA pipeline lifts IaC practice out of the “write code and hope” phase. The biggest win is moving platform knowledge out of human memory and into a repeatable policy set. If you start with a small rule set and build plan-based visibility, you can standardize security and governance decisions over time with much less friction.