Terraform ignore_changes : A Life Saver When Azure Resources Drift

 

If you work with Azure resources and Terraform, sooner or later, you’ll face this.

You deploy a resource (say, a Managed Identity) using Terraform, and after a few days, you want to add something simple — maybe a Role Assignment.

But as soon as you run:

terraform plan

Boom — you see a terrifying message:

-/+ resource "azurerm_user_assigned_identity" "my_identity" {
      id          = "/subscriptions/xxxxxxx/resourceGroups/xyz/providers/Microsoft.ManagedIdentity/userAssignedIdentities/my_identity"
      location    = "eastus"
      name        = "my_identity"
    ~ principal_id = "11111111-1111-1111-1111-111111111111" => "22222222-2222-2222-2222-222222222222"
    ~ tenant_id    = "aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa" => "aaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
}

Plan: 1 to add, 0 to change, 1 to destroy.

You start thinking —

“Wait, what?? Why will it recreate my Managed Identity? I did nothing except plan to add a role assignment!”


🚨 Scenario 1: Managed Identity “False Drift” and Dangerous Recreate

Let’s see an example:

resource "azurerm_user_assigned_identity" "my_identity" {
  name                = "my-managed-id"
  resource_group_name = azurerm_resource_group.my_rg.name
  location            = azurerm_resource_group.my_rg.location
}

resource "azurerm_role_assignment" "assign_identity" {
  principal_id       = azurerm_user_assigned_identity.my_identity.principal_id
  role_definition_name = "Contributor"
  scope              = azurerm_resource_group.my_rg.id
}

You deploy this → everything is fine.

Later when you run terraform plan, Terraform fetches principal_id and tenant_id from Azure backend.
If even a tiny representation change occurs (like casing or invisible metadata updates by Azure), Terraform thinks → “Mismatch! Resource drifted!”

Since principal_id is immutable, Terraform’s only option is to recreate the resource.
And that is the dangerous part → this is your production Managed Identity → re-creating it will break access for anything using that identity.


🚨 Scenario 2: VM Data Disk Drift and Full VM Recreate

This can happen with Azure Virtual Machines + Data Disks too.

You provision a VM with a data disk, and later Azure updates metadata like:

      • Disk lun
      • Disk id
      • VM NIC private IP (when dynamic)

When Terraform compares the state with the Azure backend, any mismatch in these immutable properties leads Terraform to plan a destroy and recreate action — even if the change is trivial or backend-controlled.

-/+ resource "azurerm_linux_virtual_machine" "example_vm" {
      id                 = "/subscriptions/xxxxxx/resourceGroups/xyz/providers/Microsoft.Compute/virtualMachines/example-vm"
      name               = "example-vm"
      location           = "eastus"
      resource_group_name = "xyz"
    ~ storage_data_disk {
          lun            = "0" => "0"
          managed_disk_id = "/subscriptions/xxxxxx/resourceGroups/xyz/providers/Microsoft.Compute/disks/example-vm-disk-old" => "/subscriptions/xxxxxx/resourceGroups/xyz/providers/Microsoft.Compute/disks/example-vm-disk-new"
          caching       = "ReadWrite"
          disk_size_gb  = 128
          create_option = "Attach"
      }
}

Plan: 1 to add, 0 to change, 1 to destroy.

This is even worse → destroying a VM unnecessarily can result in downtime or data loss if not carefully handled!


🤔 Why does Terraform behave like this?

Short answer → by design.

Terraform works in a declarative model → it assumes state == source of truth.

Azure resources often have attributes that are:

    • Computed after creation (known after apply) → like principal_id, client_id, tenant_id, resource_id, IPs.
    • Immutable → cannot be changed → if drift happens → resource must be recreated.

So, even harmless metadata drift leads Terraform to think → resource needs recreation.


✅ What to do: Always inspect Terraform Plan carefully

Your Terraform Plan output is your best friend.

Whenever a plan shows -/+ (Destroy/Create), immediately stop and check:

      • Did something really change?
        If yes → update Terraform code or import changes.
      • Or is it just backend-computed attributes drifting?
        If yes → safe to ignore using ignore_changes.

💡 Solution → Use ignore_changes Smartly

Terraform has a powerful feature called ignore_changes that saves us here.

resource "azurerm_user_assigned_identity" "my_identity" {
  name                = "my-managed-id"
  resource_group_name = azurerm_resource_group.my_rg.name
  location            = azurerm_resource_group.my_rg.location

  lifecycle {
    ignore_changes = [
      principal_id,
      client_id,
      tenant_id
    ]
  }
}

✅ Now, even if Azure updates these backend attributes → Terraform will ignore them during plan → no drift → no re-create.


⚡ Important — Don’t Overuse ignore_changes

While ignore_changes is life-saving, don’t use ignore_changes = all unless you know exactly what you are doing.

lifecycle {
  ignore_changes = all
}

➡️ This disables drift detection entirely → even for genuine changes → very dangerous → you may miss critical changes and your resources will be outdated without you knowing.

Instead:

Always specify only those backend-computed attributes which are known to cause false drift.

For example:

    • principal_id, client_id, tenant_id (for Managed Identities)
    • private_ip_address (for dynamic IP NICs)
    • disk.lun or disk.id (for VM disks in specific scenarios)

🛠️ Temporary Workaround: Using Local .terraform Module Copy

Many times, the team working with Terraform may not have modify access to backend modules. Or even if they do, updating a shared module (to add ignore_changes) may involve Pull Requests, reviews, and approvals — which can take time.

But what if you need a solution right now to execute terraform plan and terraform apply without destroying the Azure resource?

Well, there is a trick !

Whenever you run terraform init, all the backend modules (which you typically call through your main.tf or module.tf) are copied into your local working directory inside a folder called .terraform.

If you navigate to .terraform, you will find a path like this (depending on module source):

.terraform/modules/<module-name>/<...>

This is a local copy of the module source code.

✅ If you need a quick fix and cannot wait for backend module updates, you can temporarily edit this local copy of the module.

Add the ignore_changes block right inside the corresponding resource block in the local module copy.

Once done → run terraform plan again → and the issue should be resolved locally → no more destroy plan.

⚡ However, remember — this is only a temporary workaround:

    • If you delete .terraform or run terraform init -reconfigure, your changes will be lost.
    • Other team members (on their own local directories) will not get this fix unless they also update their local copies.
    • Therefore, as a permanent solution, you must eventually update the backend module itself.

Still, this trick can be a lifesaver when you need to avoid accidental resource destruction in urgent scenarios!


🎯 Summary (Rule of Thumb)

    • Always check terraform plan carefully.
    • Understand if a resource will be recreated → is that necessary or a false drift.
    • If drift is false (backend-computed attributes only) → use ignore_changes.
    • Never use ignore_changes = all unless in rare, special cases.
    • Use precise ignore_changes blocks for predictable and clean Terraform workflows.

🚀 Conclusion

Terraform’s strict declarative model is great — but in cloud platforms like Azure, computed attributes can cause unwanted drifts and dangerous re-creations.

Thankfully, ignore_changes gives us a safe and clean way to handle this → without ignoring genuine changes.

Next time you see an unnecessary resource destroy → remember this trick → and save your environment from accidental outages!

Leave a comment