- Govern the deploy, not the bill. It is cheaper to prevent a bad resource from being created than to rightsize it after the fact.
- Three ways to enforce governance in IaC, in order of sophistication:
- Terraform
validationblocks — simple, built-in, enough for most teams. - Policy-as-code (OPA or Sentinel) — for complex rules that span multiple resources.
- Enterprise governance tools (Stacklet, Adaptive6, etc.) — when you want coverage plus remediation without writing code.
- Terraform
- Pick the lightest tier that actually covers your rules. Don’t over-engineer.
Why governance belongs at deploy-time
In article #08 — Fix the root cause, not the symptoms: Cloud Governs vs Cost Optimizations, I argued that cost optimisation without governance is a treadmill. You rightsize the instance today, someone provisions the next one oversized tomorrow, and the work starts again.
Governance breaks that cycle. Instead of optimising after the fact, you prevent non-compliant resources from being created in the first place. The earliest point where that enforcement is practical is the deploy pipeline — and the cleanest place to wire it in is Infrastructure as Code (IaC).
There is a second reason to deploy via IaC that often gets understated: manual deployments create drift. Someone clicks through a console, ticks a box no one else knows about, and three months later nobody can reproduce the configuration. That drift is the enemy of every FinOps capability: allocation, optimisation, forecasting, and governance all assume that what’s documented matches what’s running. IaC makes that true by construction.
Once you’re deploying via IaC, policy enforcement is a small step away.
Three tiers of enforcement
Governance-through-code has three sensible implementation tiers. The right tier depends on the complexity of your rules, not on how impressive you want to sound in an architecture review.
| Tier | Tool | Good for | Effort |
|---|---|---|---|
| 1 | Terraform validation blocks | Per-variable rules (allowed instance types, regions, sizes) | Very low |
| 2 | Policy-as-code (OPA, Sentinel) | Cross-resource rules, complex conditions, organisation-wide policy | Medium |
| 3 | Enterprise governance platform | Full coverage, remediation, reporting, non-IaC assets | Low effort, higher cost |
Walk up the tiers only when the lower tier can’t express the rule you need.
Tier 1 — Terraform validation blocks
The simplest and most underused tool. Terraform has validation blocks built into variable definitions. You constrain what values are allowed; anything outside the list fails at terraform plan time.
Example — restricting which EC2 instance types can be deployed:
variable "instance_type" {
type = string
validation {
condition = contains(["t3.micro", "t3.small", "m5.large"], var.instance_type)
error_message = "Instance type must be one of: t3.micro, t3.small, m5.large."
}
} For rules with more values, use a list variable so the allowed set is easy to read and extend:
variable "allowed_regions" {
type = list(string)
default = ["us-east-1", "us-west-2", "eu-central-1"]
}
variable "region" {
type = string
validation {
condition = contains(var.allowed_regions, var.region)
error_message = "Region must be one of the allowed regions."
}
} Better still — wrap the governed resource in a module so the validation logic lives in one place and every downstream consumer inherits it:
# modules/governed_instance/main.tf
variable "instance_type" {
type = string
validation {
condition = contains(["t3.micro", "t3.small", "m5.large"], var.instance_type)
error_message = "Instance type must be one of: t3.micro, t3.small, m5.large."
}
}
resource "aws_instance" "example" {
instance_type = var.instance_type
# ... other config ...
}
# In the consumer's Terraform:
module "my_governed_instance" {
source = "./modules/governed_instance"
instance_type = "t3.micro" # valid
# instance_type = "m5.24xlarge" # would fail validation
} When tier 1 is enough: your rules are per-variable (“no instances bigger than X”, “only these regions”, “only these OSes”). This is usually 70–80% of what a starting governance practice actually needs.
When tier 1 is not enough: rules that span multiple resources (“if this bucket is public, then it must be tagged with data-classification = public”), rules that depend on metadata outside Terraform, or rules that need to be applied org-wide across many teams without trusting each team to include the validation.
Tier 2 — Policy-as-code (OPA and Sentinel)
For anything beyond per-variable rules, step up to policy-as-code. Two dominant options:
- HashiCorp Sentinel — native to Terraform Cloud / Terraform Enterprise.
- Open Policy Agent (OPA) — open source, cloud-agnostic, works with Terraform via
conftestor similar.
Both let you write policies in a declarative language and evaluate them against the Terraform plan. If the plan violates a policy, the apply is blocked.
Example — a Sentinel rule requiring all EC2 instances to be t3.micro:
import "tfplan"
main = rule {
all tfplan.resources as _, resources {
all resources as _, r {
r.type is "aws_instance" and
r.attributes.instance_type else "t3.micro" is "t3.micro"
}
}
} The value of policy-as-code is that it runs in the CI/CD pipeline, outside the team’s own Terraform code, so compliance doesn’t depend on individual developers remembering to include the right validation block. Pull request → plan → policy evaluation → merge or block. Governance becomes part of the pipeline, not a convention.
When tier 2 fits: you have a central governance function that needs to enforce rules across teams you don’t directly control, or your rules involve cross-resource conditions that validation blocks can’t express.
Tier 3 — Enterprise FinOps governance tools
At some scale, writing and maintaining your own policy library becomes its own project. At that point, paid tooling starts making sense.
A few names in the space: Stacklet, Adaptive6, and others. These platforms offer:
- Pre-built policy libraries covering common FinOps and security rules
- Policy enforcement across IaC and already-deployed resources
- Remediation (not just detection — they can actually fix or flag things)
- Reporting, dashboards, and audit trails
When tier 3 is worth the spend: you have a multi-team, multi-account, multi-cloud setup where the cost of maintaining your own policy code is higher than the subscription fee. Or you want to govern resources that weren’t deployed via IaC (lift-and-shift workloads, legacy assets, console-created resources).
I’ll avoid picking a winner between vendors — the right one depends on your stack and your budget. But the general point stands: at a certain size, building it yourself stops being cost-effective.
Matching the tier to the rule
Not every rule needs a heavyweight solution. A small team with a handful of clear constraints can go very far on tier 1 alone. A central platform team governing dozens of delivery teams will outgrow tier 1 within months.
A quick decision path:
Can the rule be expressed as "this variable must be in this list"?
├── Yes → Tier 1 (validation blocks)
└── No
│
├── Does the rule span multiple resources or depend on logic?
│ ├── Yes → Tier 2 (OPA / Sentinel)
│ └── No → Tier 1 (probably a reusable module)
│
└── Is the rule needed across teams you don't directly control,
or across non-IaC-deployed resources?
├── Yes → Tier 3 (enterprise tool)
└── No → Tier 2 Don’t reach for tier 3 because it sounds impressive. Reach for it when tier 1 and tier 2 genuinely run out of road.
What if you don’t use IaC?
Fair question. If you don’t deploy via IaC, none of the mechanisms in this article apply at deploy-time.
In my view, that’s a problem worth fixing before you worry about governance — manual deployments are already a drag on allocation, optimisation, and forecasting, not just governance. But it’s a real situation, and there is a way to govern non-IaC deployments.
That’s the topic of Part Two, which covers governance via cloud IAM policies — enforcement from the account/project side rather than the deploy-pipeline side. Different tool, same goal: prevent the bad resource from existing in the first place.
Summary
- Govern at deploy-time. It’s cheaper than optimising after the fact.
- Deploy via IaC. That’s the entry condition for most governance-through-code approaches.
- Start with Terraform
validationblocks. Most small-to-mid teams don’t need more. - Graduate to OPA/Sentinel when rules outgrow per-variable validation.
- Reach for enterprise tools only when the scale and complexity justify the spend.
- Pick the lightest tier that covers your rules. Don’t over-engineer governance.
Thanks for reading. If this helped, share it. Questions or topic suggestions — send them through.