Blog · CI/CD

How to Add Cost and Security Guardrails to Your Terraform CI/CD Pipeline

A hands-on walkthrough of wiring Infracost, Checkov, and policy-as-code into Terraform CI so issues are caught before merge, not in a postmortem.

10 min read

Why gate at the pull request, not after merge

Most teams find out a Terraform change was expensive or insecure one of two ways: the cloud bill spikes at the end of the month, or a security scanner run days later flags something already live. Both are the same mistake — checking cost and security after the infrastructure exists instead of before it's created. Every guardrail in this post runs at pull-request time, when a resource is still a plan, not a running bill.

1. Cost estimation with Infracost

Infracost reads a Terraform plan's JSON output and prices every resource in it against current cloud pricing, then reports the monthly delta. Wired into CI, it turns "this PR adds infrastructure" into "this PR adds €340/month," attached to the diff instead of discovered a billing cycle later.

# minimal CI step
terraform plan -out=plan.tfplan
terraform show -json plan.tfplan > plan.json
infracost breakdown --path plan.json --format json > infracost.json

The output alone isn't a guardrail until it has a threshold attached — otherwise it's a number nobody acts on. Set both a warn and a block level:

# .github/driftguard.yml
cost:
  currency: USD
  warn_above_monthly_delta: 100
  block_above_monthly_delta: 1000

Exchange rates matter if your team reports in a non-USD currency — fetch them daily rather than hardcoding a rate that drifts (ironically) out of date. See the cost analysis docs for the full config.

2. Static security analysis with Checkov

Checkov scans Terraform (and CloudFormation, Kubernetes manifests, and more) against a large library of policy checks — public storage buckets, security groups open to the world, unencrypted volumes, overly broad IAM. It's deterministic and fast enough to run on every PR without slowing the pipeline down.

# minimal CI step
checkov -d . --framework terraform --compact --quiet

Static rules catch known-bad patterns reliably but miss context-dependent issues — an IAM policy that's technically valid but broader than the service actually needs, for instance. Pair static analysis with a human reading any IAM or network-boundary diff; don't rely on it as the only check on access changes.

3. Policy-as-code for team-specific rules

Cost thresholds and security scanners cover the general case. Policy is where you encode what's specific to your org — which resource types need a second approval, which environments are locked down, which teams own which blast radius. Simple deny/warn glob patterns cover most of this:

# .github/driftguard.yml
policy:
  block:
    - "aws_db_instance.*.publicly_accessible"
    - "aws_iam_policy.*"
  warn:
    - "aws_instance.*.instance_type"

Patterns are evaluated against every resource change in the Terraform plan; wildcards match any value. For rules that need real logic — multi-environment policies, team-based ownership, time-based restrictions (no production changes after 5pm Friday) — a full OPA/Rego policy bundle handles it. See the policy engine docs.

4. Least-privilege credentials for the pipeline itself

A guardrail that needs a long-lived cloud key to run is itself a security liability. Cost and drift checks that read live infrastructure state should use short-lived, read-only credentials — AWS STS AssumeRole, GCP Workload Identity Federation, or Azure federated workload identity — scoped to read-only, issued per run, and never stored. If a pipeline step only needs the plan JSON (cost, static security), it doesn't need cloud credentials at all.

Putting it together

A pull-request pipeline with all three guardrails, roughly in the order that fails fastest first:

  • terraform plan → generate the plan JSON everything else reads from.
  • Checkov → static security scan, blocks on high severity.
  • Infracost → cost delta, warns or blocks on your configured thresholds.
  • Policy check → team-specific block/warn rules against the plan diff.
  • Drift check (if a cloud role is connected) → compare live resource state against the plan's starting assumptions.

Each step is independently useful; together, they replace "we found out after merge" with "we found out before anyone approved it."

DriftGuard runs cost, security, policy, and drift checks on every Terraform PR out of the box — no pipeline YAML to assemble.

Terraform CI/CD: Cost & Security Guardrails | DriftGuard · DriftGuard