How to Reconcile Terraform State Drift
Terraform state drift means what the state file thinks exists no longer matches what is actually on disk or in your infrastructure. When that happens, your next apply either stomps the manual change or errors out. Here is how to detect drift, decide which version wins, and get back to a clean plan.
What terraform state drift is
Terraform tracks your infrastructure in a state file. When an engineer manually edits a resource outside of Terraform - hotfixing a config file during an incident, for example - the state file is stale. The real world and the IaC description are out of sync. That is drift.
Running terraform plan exposes it immediately:
terraform plan
~ resource "local_file" "app_config" {
~ content = "debug=false\nlog_level=info" -> "debug=true\nlog_level=debug"
filename = "/tmp/app.conf"
}
The ~ means "will change." The left side is what state thinks exists;
the right side is what Terraform would write on the next apply. If the
manual hotfix is on the right side, an unconsidered apply reverts it.
Two ways to resolve drift
Option 1 - Ratify the manual change (keep what is on disk). Update your Terraform code to declare what already exists, then apply:
resource "local_file" "app_config" {
content = "debug=true\nlog_level=debug" # matches the hotfix
filename = "/tmp/app.conf"
}
terraform apply -auto-approve
State, disk, and code all agree. terraform plan now shows No changes.
Option 2 - Let Terraform win (revert the manual change). Leave the code as-is and apply. Terraform overwrites the file with what the state (and code) say it should be. The manual change is gone.
terraform apply -auto-approve # reverts disk to what main.tf declares
Confirming the drift is gone
Use the strict exit-code flag to verify the plan is truly clean:
terraform plan -detailed-exitcode
echo $? # 0 = no changes, 1 = error, 2 = changes pending
Exit code 0 is your success signal.
A word on terraform refresh
terraform refresh pulls the real-world state into the state file without
changing any resources. It is useful for seeing what drifted, but it does
NOT fix drift by itself - your code still declares the old value. You still
need to update the code (option 1) or apply to revert (option 2).
Preventing drift
The structural fix is restricting who can touch infrastructure at all.
If the only path to change is a pull request through Terraform, drift
cannot happen silently. Drift-detection tools (Atlantis, env0, Spacelift,
or a scheduled terraform plan in CI) can alert you when something drifts
between planned runs.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Running terraform plan to detect drift and read which side is state vs disk
- Updating Terraform code to ratify a manual change, then applying to sync state
- Using terraform plan -detailed-exitcode to confirm a clean zero-change plan
FAQ
What is Terraform state drift?
Drift is when the real state of a resource differs from what the Terraform state file records. It usually happens after a manual change is made to infrastructure outside of Terraform - for example, editing a config file by hand on a server.
How do I fix Terraform state drift?
Decide which version wins: if you want to keep the manual change, update your Terraform code to declare the current real-world value, then run terraform apply. If you want Terraform to win, run terraform apply as-is and it reverts the resource to what the code declares.
What does terraform plan -detailed-exitcode mean?
The -detailed-exitcode flag makes terraform plan return exit code 0 for no changes, 1 for an error, and 2 for pending changes. Exit code 0 is the clean-plan signal you check for in CI or after reconciling drift.
What is state drift in Terraform?
State drift is when real infrastructure no longer matches Terraform's recorded state - usually because someone changed it manually. terraform plan shows the difference; you then import/accept the change or re-apply to restore the declared config.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →