Terraform makes intended infrastructure explicit, but production never stands still. Operators respond to incidents, cloud services apply defaults, autoscaling changes capacity, another automation updates a policy, or an urgent console edit survives long after the emergency. The resulting gap between configuration and reality is infrastructure drift.
Detecting that gap is easy to describe and surprisingly difficult to operate well. A scheduled plan can reveal differences, but it cannot tell you automatically whether a difference is dangerous, expected, temporary, or owned by another system. A useful drift program combines technical comparison with production context.
Drift is a state relationship, not a single event
Think of Terraform configuration, Terraform state, and the provider’s live resource data as three related views. Configuration describes intent. State records Terraform’s last known mapping. The provider reports what exists now. Drift appears when these views no longer agree after refresh.
Not every difference is an unauthorized console edit. A resource can drift because a managed service changes a computed field, an external controller owns part of the object, or configuration stopped declaring a value that remains in production. Start with evidence rather than assuming operator error.
Operational rule: detection should be automatic, but remediation should be proportional to confidence and impact. A clean plan output is not sufficient authorization to change production.
Why production infrastructure drifts
- Emergency intervention: an engineer changes capacity, routing, access, or protection during an incident.
- Competing automation: a platform controller, policy engine, or cloud-native service manages the same property.
- Incomplete ownership: a resource is imported partially, created outside Terraform, or shared between stacks.
- Provider behavior: defaults, normalization, computed values, and provider upgrades change the observed representation.
- Failed or interrupted execution: the live operation completed only partly or state persistence failed.
- Configuration lifecycle errors: a branch, workspace, variable set, or backend no longer matches the deployed environment.
The cause changes the remedy. Reverting an incident safeguard because it is “not in code” can recreate the outage. Accepting every live value into configuration can preserve an insecure exception indefinitely.
Build a repeatable detection workflow
- Select the correct execution context. Use the same Terraform version, provider lock file, backend, workspace, variables, credentials, and environment boundaries as the controlled deployment path.
- Refresh live resource data. Run a non-mutating plan that reads provider state and produces a machine-readable result. Protect credentials and treat plan artifacts as potentially sensitive.
- Normalize the difference. Convert provider-specific plan output into resources, attributes, operations, dependencies, and uncertainty. Suppress only differences with an explicit ownership reason.
- Enrich with production context. Attach account, region, service, environment, owner, criticality, recent changes, customer path, and maintenance-window data.
- Create a reviewable record. Preserve when the drift was first and last observed, who reviewed it, which evidence supported the decision, and how it was resolved.
Classify differences before setting priority
Resource count alone is a poor severity measure. A single public-access policy change may matter more than hundreds of autoscaling values. Classify each difference along several axes:
- Security and access: identity policies, public exposure, encryption, secrets, and network boundaries.
- Availability: redundancy, capacity, health checks, failover, deletion protection, and routing.
- Data durability: storage class, backup, replication, retention, lifecycle, and destructive replacement.
- Cost and capacity: instance families, desired counts, reserved resources, and high-volume services.
- Operational ownership: whether Terraform, another controller, the provider, or a documented human process owns the field.
Then combine the technical category with blast radius, change recency, internet exposure, environment, and confidence. New drift on a shared production gateway deserves a different response from a known computed-value difference in a development account.
Choose a safe remediation path
Return production to declared intent
Use this path when configuration remains authoritative and the live difference is unwanted. Open a reviewed infrastructure change, preview downstream effects, apply through the normal delivery system, and verify both service health and a clean follow-up plan.
Adopt the live value into code
Sometimes the production intervention is now the desired state. Translate it into reviewed configuration, document why the intent changed, and test the plan before applying. Do not simply copy every observed value; preserve only deliberate decisions.
Correct the ownership model
If another controller legitimately owns the property, encode that boundary explicitly. Narrow lifecycle ignores to the exact attribute, document the external owner, and monitor the external system. Broad ignore rules make the plan quiet by hiding real risk.
Repair state carefully
Imports, moves, and state operations can restore resource mapping, but they operate on the control record itself. Back up state, serialize access, use documented commands, and validate the resulting plan before any mutation.
Make drift detection continuous
Run detection on a cadence matched to the environment and trigger it after high-risk out-of-band events. Production may need hourly or event-driven checks; low-risk development environments may need daily checks. Also scan before an infrastructure deployment so unrelated drift cannot hide inside the proposed plan.
Route findings to accountable teams instead of a central backlog with no owners. Group repeated observations into one durable record, set escalation windows by severity, and close only when the live system, configuration, and ownership model agree.
Measure control, not noise
- Time from first drift to detection and from detection to triage.
- Open drift by severity, environment, age, and accountable service.
- Percentage of findings with a known owner and explained cause.
- Recurrence after remediation, especially for the same attribute and resource class.
- Suppression coverage with documented ownership and expiration.
- Incidents and failed changes where previously known drift was a contributing factor.
The goal is not permanent zero drift at any cost. The goal is that important differences are discovered quickly, interpreted with the right context, and resolved through an intentional, auditable production change.