TRT
Sign in
System Design

Infrastructure Changes Need Reviewable State

Infrastructure as code describes resources and their relationships in versioned files.

The Runtime Theory Team5 min read#infrastructure-as-code#drift#automation

Infrastructure as code describes resources and their relationships in versioned files. A planning step compares the declared target with provider state and proposes changes. That turns infrastructure into something reviewable — a diff, a pull request, an approval — and makes rollback a commit away. The plan is only as trustworthy as its inputs and state handling, which is where the real failure modes live.

State is the identity of what you built

The tool needs to know that this security group in the plan is that security group in the cloud, not a new one. That mapping lives in a state file, keyed by resource addresses:

hcl
# declared target — versioned, reviewed, applied
resource "aws_db_instance" "orders" {
  identifier          = "orders"
  instance_class      = "db.t4g.medium"   # 2 vCPU, 4 GiB — was db.t4g.small
  multi_az            = true
  deletion_protection = true
}
 
resource "aws_db_replica" "orders_read" {
  count               = 1                 # added in this change
  source_identifier   = aws_db_instance.orders.identifier
}

plan is the diff between this file and the provider's real resources. A change that adds a read replica can be reviewed for cost (~$0.06/hr on a t4g medium → ~$44/month), networking (security groups, subnets), and failover consequences before anything is created. State must therefore be remote and locked: shared in durable storage so it survives laptops, and locked so two concurrent applies don't both decide to create a replica and fight over the address.

Drift: the thing that makes plans lie

Drift is real infrastructure that changed outside the declared workflow — a hotfixed security group, a manually scaled instance, an auto-created snapshot. The plan refreshes its view of reality first, so drift shows up as a diff — but only if someone runs a plan.

text
day 1   : terraform apply → 0 added, 3 changed, 0 destroyed  ✓ clean
day 40  : on-call adds port 2222 to the SG by hand (break-glass)
day 41  : next plan → 1 to change (egress rule reverted)      ← detected
          if nobody plans for 6 months, the change is invisible
          and the next apply silently reverts the break-glass rule

Run a scheduled plan in CI (daily, or on a timer) purely for drift detection, and treat unexplained diffs as findings. The nasty case is not drift that gets reverted — it is automation that depends on drifted state (an IP allowlist someone added manually) being corrected without anyone connecting the outage to the apply.

Reviewing a plan that wants to destroy something

The check question is the everyday case: a plan proposes replacing a production database because of a naming change. Most providers force replacement when an immutable attribute changes — name, AZ, engine version — and the plan's destroy/create pair for a database means data loss unless a snapshot-and-restore path exists.

Before approving, reviewers should look at:

text
terraform plan -json | jq '.resource_changes[] | select(.change.actions | index("delete"))'
 
does any action = ["delete","create"] ?      ← replacement, not update
is deletion_protection on the resource?      ← would block it at apply time
is there a from_snapshot / snapshot on destroy?
does the change touch a state address that  ← renaming an address looks
  other resources reference?                 like delete+create too

Two tools preserve identity when it's only the reference that changed:

hcl
# terraform ≥ 1.1 — move the address without replacing the resource
moved {
  from = aws_db_instance.main
  to   = aws_db_instance.orders
}

terraform state mv does the same interactively (destructive to state if mistyped — take a backup first: terraform state pull > state.backup.json). The rule generalizes: an address change is a data-destroying change until proven otherwise.

Costs and failure cases

  • Secrets in state. State files record resource attributes verbatim — database passwords, generated keys, connection strings. Encrypt state at rest, restrict who can read it, and keep real secrets in a secrets manager referenced by data source rather than written into the configuration. Plain-text secrets in version control are a one-commit compromise.
  • Broad plans. A plan showing 24 to change, 0 to destroy is not automatically safer than one showing a replacement — a bulk IAM or network change can have a wider blast radius than a single replacement. Review by resource type, not by count.
  • Plan ≠ apply. Drift between plan and apply (or a concurrent change) is why apply runs from the same pipeline that produced the plan, with no manual edits in between.
  • IaC doesn't fix bad design — it makes a bad architecture reproducible across every environment you have.

Key takeaways

  • State is identity. Remote, locked, versioned state is what lets a plan say "change" instead of "create another one" — and what makes concurrent teams safe.
  • Review the actions, not the summary. Any delete+create pair on stateful resources is a data-loss candidate; use moved blocks or state mv to preserve identity.
  • Drift detection needs a schedule, or your declared state quietly stops describing reality — and the next apply reverts somebody's break-glass change.
  • State files are secret-bearing artifacts. Encrypt, restrict, and keep real credentials out of both state and configuration.

Terraform Documentation: Infrastructure as Code covers state, plans, and moved blocks.

Spotted a mistake or something broken? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save