Skip to content

Drift Detection and Import

Configuration drift is what happens when reality diverges from your Terraform config without Terraform’s knowledge, and terraform import (or the declarative import block) is how you bring an already-existing resource under Terraform’s management in the first place.

Drift happens whenever someone or something changes a resource outside of Terraform. A common case on Google Cloud: an engineer opens the Console under time pressure and edits a Compute Engine instance’s labels directly, or resizes its machine type, without touching the Terraform configuration that supposedly manages that instance. Terraform’s recorded state now disagrees with the real object.

Nothing alerts you to this automatically. The next time someone runs:

Terminal window
terraform plan

Terraform refreshes its view of the real resource, compares it against both the configuration and the last recorded state, and surfaces the difference as a plan — typically an “in-place update” that would revert the manual change back to what the configuration says, since Terraform assumes the configuration is the source of truth.

Sometimes you want the opposite: acknowledge the manual change and just bring Terraform’s state back in sync with reality, without touching any actual infrastructure. That is what -refresh-only mode is for:

Terminal window
terraform apply -refresh-only

This updates only the state file’s recorded attributes to match what is really running in GCP right now. It does not create, update, or destroy any real resource — it is purely a reconciliation of Terraform’s memory with the world.

Bringing existing resources under management with terraform import

Section titled “Bringing existing resources under management with terraform import”

Plenty of GCP resources get created before Terraform ever exists for a project — a Compute Engine instance someone clicked through in the Console, a Cloud Storage bucket created by an early script. The CLI terraform import command associates one of those existing, real resources with a resource address already written in your configuration:

Terminal window
terraform import google_compute_instance.web projects/my-project-id/zones/us-central1-a/instances/web-vm

This is easy to get wrong in one important way: terraform import only populates the state entry. It does not write any HCL for you. You still have to hand-write a resource "google_compute_instance" "web" { ... } block yourself, with attributes close enough to the real object that the next plan does not show a huge diff. Get the config wrong and your very next apply might try to “fix” the resource to match a config that never actually described it.

Terraform 1.5 introduced a second way to import that most teams now prefer for anything worth reviewing: the import block, written directly in a .tf file alongside your other configuration:

import {
to = google_compute_instance.web
id = "projects/my-project-id/zones/us-central1-a/instances/web-vm"
}

The advantage over the CLI command is entirely about review. A CLI terraform import run is an immediate side effect — it writes to state the moment you run it, with no preview step. An import block, by contrast, is just more configuration: it shows up in the output of terraform plan as a planned import, exactly like a planned create or update, so a teammate can review it in a pull request before anyone runs apply. Terraform can even generate a starting resource block for you from the real object’s attributes using terraform plan -generate-config-out=generated.tf, which you then review and clean up rather than writing from scratch.

Reorganizing state without touching infrastructure

Section titled “Reorganizing state without touching infrastructure”

Two more terraform state subcommands round out this lesson, and both are the mirror image of each other:

Terminal window
# Rename or move a resource's address in state, e.g. after refactoring into a module
terraform state mv google_compute_instance.web module.app.google_compute_instance.web
# Stop tracking a resource in state, without destroying the real object
terraform state rm google_compute_instance.web

terraform state mv changes only the address Terraform uses to refer to a resource in state — useful when you restructure a configuration into modules and do not want Terraform to think it needs to destroy the old address and create a new one. terraform state rm is the direct opposite of import: it deletes a resource’s entry from state so Terraform no longer manages it, but it has zero effect on the real GCP object, which keeps running exactly as it was, now simply unmanaged.

flowchart LR
  manual["Instance label edited in Console"] --> plan["terraform plan"]
  plan -->|"diff shown"| drift["Drift detected"]
  existing["Existing untracked GCP resource"] --> importBlock["import block or terraform import"]
  importBlock -->|"state entry created"| state["terraform.tfstate"]
A manually changed resource surfaces as drift on plan, while an existing untracked resource is brought in with import
A teammate manually edits a Compute Engine instance in the GCP Console. How does Terraform surface this configuration drift
What is the key difference between the CLI terraform import command and a declarative import block
What does terraform state rm do to the real GCP resource it is pointed at
What does terraform apply -refresh-only do