Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Terraform By James Joyner IV · · 9 min read

Large Terraform Refactors With Moved and Import Blocks

Renaming resources or absorbing existing infra used to mean scary state surgery. Moved and import blocks make large refactors reviewable and safe. Here's my playbook.

  • #terraform
  • #refactoring
  • #moved-blocks
  • #import
  • #state
  • #devops

For most of my career, refactoring a large Terraform estate meant a tense session of terraform state mv commands run by hand, praying I typed the addresses right. One fat-fingered move and a resource gets orphaned, then destroyed and recreated. Configuration-driven moved and import blocks changed that completely. Now refactors go through code review like everything else.

Here’s how I run large refactors without the cold sweat.

The problem with manual state surgery

When you rename a resource or move it into a module, Terraform sees the old address disappear and a new one appear. Its instinct is to destroy the old and create the new — catastrophic for a database or a load balancer.

The old fix was imperative:

terraform state mv aws_instance.web module.web.aws_instance.this

This works, but it’s invisible to review, runs outside CI, and isn’t repeatable. On a big refactor touching dozens of resources, that’s a recipe for an orphan.

Moved blocks: refactoring as code

A moved block tells Terraform “this resource changed addresses; don’t destroy anything.” It lives in your config, so it’s reviewed, versioned, and applied by CI:

moved {
  from = aws_instance.web
  to   = module.web.aws_instance.this
}

Now terraform plan shows a clean move instead of a destroy/create, and your reviewer sees exactly what’s happening. When you rename for clarity:

moved {
  from = aws_s3_bucket.logs
  to   = aws_s3_bucket.application_logs
}

I keep moved blocks in the same PR as the rename, let it ride for a release or two so all workspaces apply it, then prune them once everything’s converged.

Import blocks: absorbing existing infrastructure

The flip side is bringing already-existing resources under Terraform management. The old terraform import CLI command was imperative and untracked. Import blocks make it declarative:

import {
  to = aws_security_group.legacy_api
  id = "sg-0a1b2c3d4e5f"
}

resource "aws_security_group" "legacy_api" {
  name   = "legacy-api"
  vpc_id = "vpc-0998877"
  # ... matching config
}

On the next plan, Terraform reads the real resource and shows you the diff between it and your HCL. This is the magic: you iterate on the config until the plan is clean, then apply. No more importing first and discovering the config is wrong second.

Generating config for imports

Writing HCL to match an existing resource by hand is miserable. Terraform can scaffold it:

terraform plan -generate-config-out=generated.tf

This emits HCL for every import block whose resource doesn’t yet exist in config. It’s never perfect — you’ll clean up computed fields and references — but it turns a two-hour transcription job into a ten-minute edit.

A safe refactor playbook

The sequence I follow on every large refactor:

  1. Snapshot state. Confirm the backend bucket is versioned, or take a manual backup.
  2. Write the moved/import blocks alongside the code change in one PR.
  3. Plan and read it line by line. The plan must show moves and imports — zero unexpected destroys.
  4. Get review. Because it’s all in code now, a colleague can actually review it.
  5. Apply in a low-traffic window.
  6. Prune the moved/import blocks once all workspaces have converged.

The rule I never break: if the plan shows a destroy of a stateful resource, stop. That’s a missing moved block, not an acceptable plan.

Where AI earns its keep

Large refactors are exactly where AI shines, because the work is mechanical and pattern-heavy. I paste in the old and new addresses and ask it to generate every moved block. I paste a list of resource IDs and the target addresses and ask for the import blocks. It handles the tedium reliably.

It’s also good at auditing a refactor plan: “Scan this plan output and flag any resource being destroyed that should have been a move.” That catches the orphan before it happens. I keep these patterns in my Terraform prompts and route the final PR through our Code Review tool for a structured second pass.

As always: AI drafts the blocks, I read the plan, I run the apply.

The takeaway

moved and import blocks turned Terraform refactoring from risky CLI surgery into reviewable, versioned code. Write them in the PR, generate config for imports, read every plan line, and never accept an unexpected destroy on a stateful resource. Done this way, even a refactor spanning hundreds of resources is just another pull request.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.