Drift, Refresh and Reconciliation
Terraform operates across three related but distinct representations of infrastructure:
Terraform Configuration
│
│ desired configuration
▼
Terraform State
│
│ recorded understanding
▼
Real InfrastructureThese are connected, but they are not identical. Production operations become much easier to reason about once you understand how they can diverge.
What Is Infrastructure Drift?
Drift occurs when real infrastructure changes outside the Terraform workflow and Terraform’s recorded understanding no longer matches reality.
For example:
Terraform configuration
instance_type = "t3.micro"
Terraform state
instance_type = "t3.micro"
Real infrastructure
instance_type = "t3.large"Someone may have changed the instance type manually in a cloud console.
Other sources of drift include:
- another automation system changing a resource
- a resource being deleted manually
- a cloud service changing something automatically
- an administrator modifying configuration
- a separate Terraform project managing the same object
- external systems modifying attributes Terraform also manages
Drift is therefore not necessarily a Terraform bug. It is a consequence of multiple systems being capable of changing the same infrastructure.
Configuration, State, And Reality
It helps to separate four questions.
What Do I Want?
Terraform configuration expresses desired infrastructure.
resource "terraform_data" "example" {
input = "desired"
}What Does Terraform Remember?
Terraform state records Terraform’s understanding of managed resources.
What Exists Right Now?
The provider can query the actual infrastructure.
What Should Terraform Do About The Difference?
Terraform compares information and produces a plan.
That gives us:
Desired configuration
│
├───────────────┐
│ │
▼ ▼
Terraform state Real infrastructure
│ │
└────── compare ┘
│
▼
PlanDetecting Drift With terraform plan
One of the simplest ways to discover drift is:
terraform planTerraform refreshes its understanding of managed resources as part of normal planning behavior and compares the resulting information with configuration.
Suppose your configuration says:
resource "terraform_data" "example" {
input = "original"
}After applying it, someone changes the external object.
When you run:
terraform planTerraform can detect that the current infrastructure differs from what the configuration describes and propose a reconciliation.
The exact plan depends on the resource and provider, but conceptually:
Configuration: original
State before refresh: original
Real infrastructure: changed
↓
Terraform refreshes
↓
State understanding: changed
↓
Configuration differs
↓
Plan proposes correctionThis is why drift detection is not simply “compare the state file with the cloud.” Terraform needs current provider information to understand what exists now.
Refresh Terminology Has Changed
Older Terraform tutorials often teach a separate workflow:
terraform refreshThat is outdated as the primary way to think about current Terraform workflows.
Modern Terraform performs refresh-related behavior as part of planning and applying, and provides explicit refresh-only operations when you want to update state without applying configuration changes.
The important current commands are:
terraform plan -refresh-onlyand:
terraform apply -refresh-onlyThe first lets you review what updating Terraform’s state from remote infrastructure would change.
The second applies that refresh-only operation.
Note
Be careful with older tutorials that describe terraform refresh as the normal standalone workflow. Current Terraform encourages you to think in terms of normal plan/apply refresh behavior and explicit -refresh-only operations when you intentionally want a refresh-only workflow.
What Does terraform plan -refresh-only Do?
A refresh-only plan asks Terraform to update its understanding of remote objects without applying the configuration’s desired changes.
Run:
terraform plan -refresh-onlyConceptually:
Real infrastructure
↓
Provider reads current values
↓
Terraform calculates state changes
↓
You review the refresh-only planThis is useful when you want to understand what changed in the remote infrastructure and how Terraform’s state would be updated.
You can then apply that state refresh deliberately:
terraform apply -refresh-onlyThe distinction matters because refreshing state is not the same as reconciling infrastructure to configuration.
Four Different Operations
These four concepts are easy to confuse.
Detecting Drift
You discover that infrastructure differs from the expected state.
A normal plan is often the most useful mechanism.
terraform planRefreshing State
You update Terraform’s recorded information to reflect current remote infrastructure without intentionally applying configuration changes.
terraform plan -refresh-only
terraform apply -refresh-onlyReconciling Infrastructure
You decide that Terraform configuration is authoritative and want real infrastructure brought back into compliance.
terraform plan
terraform applyIntentionally Accepting The External Change
You decide that the external change is actually the new desired state.
In that case, update the Terraform configuration and then plan:
External change
↓
Review
↓
Decide it is intentional
↓
Update Terraform configuration
↓
terraform planDo not simply refresh state and call the problem solved. If the configuration still expresses the old desired state, the next normal plan may propose changing the infrastructure again.
A Small Drift Example
Create:
terraform-drift-example/
└── main.tfUse:
terraform {
required_version = ">= 1.6.0"
}
resource "terraform_data" "example" {
input = "original"
}Initialize and apply:
terraform init
terraform applyNow imagine an external system changes the managed object’s effective value. With a real provider resource, this could happen through a cloud console or another automation system.
The workflow becomes:
Terraform configuration
│
│ "original"
▼
Real infrastructure
│
│ external change
▼
Real infrastructure
│
│ now differs
▼
terraform plan
│
▼
Terraform proposes reconciliationThe example is intentionally provider-independent. The production lesson is more important than a contrived cloud resource: external changes create an ownership question.
Reverting Infrastructure To Configuration
Suppose the external change was accidental.
Then Terraform configuration remains authoritative:
External change
↓
terraform plan
↓
Unexpected difference identified
↓
Review
↓
terraform apply
↓
Infrastructure returns to desired configurationThis is reconciliation.
The important safety step is reviewing the plan before applying it. Drift can involve destructive or expensive changes.
Warning
Do not blindly apply a drift-reconciliation plan. The external change may have been intentional, and Terraform may propose a replacement or another disruptive operation.
Updating Configuration To Match Reality
Sometimes the external change is intentional.
For example:
Old Terraform configuration
instance size = small
External change
instance size = medium
Decision
medium is now the intended production configurationIn that case, update the Terraform configuration to medium.
Do not merely refresh state and leave configuration saying small.
The desired workflow is:
Real change
↓
Review and approve intent
↓
Update Terraform configuration
↓
Plan
↓
No unexpected reconciliationThat preserves Terraform as the source of declared intent.
Drift Detection In CI/CD
Drift can remain invisible if Terraform is only executed when someone submits a code change.
A production team can instead schedule drift detection.
Conceptually:
Scheduled CI job
↓
terraform plan
↓
Unexpected changes?
/ \
no yes
│ │
done alert/reviewThe CI job does not necessarily need to apply anything. Detection and remediation can remain separate.
This is often safer:
Detect
↓
Notify
↓
Investigate
↓
Decide
↓
Reconcile intentionallyRather than:
Detect
↓
Automatically overwrite everythingThe right policy depends on the ownership model and risk of the infrastructure.
When Drift Is A Design Problem
Repeated drift is often a sign that ownership is unclear.
Imagine:
Terraform ──────┐
├── same resource
Admin console ──┤
│
Automation ─────┘If all three systems are allowed to modify the same attributes, Terraform will continually fight the other systems.
Possible solutions include:
- establish Terraform as the owner
- move the external change into Terraform
- divide ownership by attribute or resource where the provider supports it safely
- change the architecture so only one system manages a resource
- intentionally use lifecycle behavior where appropriate
The worst solution is often simply hiding every difference.
Drift And Operational Ownership
A healthy production process asks:
Who owns this resource?
Who is allowed to change it?
How are changes reviewed?
How are external changes detected?
What happens when drift is found?Terraform is most predictable when ownership is explicit.
The operational mental model is:
Configuration
│
│ desired state
▼
Terraform
│
├── reads real infrastructure
│
├── compares state
│
└── produces a plan
│
▼
human decision
/ \
reconcile accept change
│ │
apply update configurationDrift management is therefore not just a command. It is an operating model for deciding which system is authoritative.