moved blocks for refactoring: what developers should know
Refactoring infrastructure code is risky. Rename a resource in Terraform and you'll trigger a destroy-then-recreate cycle that can cause downtime or data loss. That's where moved blocks come in—a feature that lets you restructure your Terraform without destroying the underlying cloud resources. We'll walk through how to use them safely, integrate them into your pull request workflow, and clean them up once your team has converged.
Understanding moved blocks: from and to
The moved block is Terraform's way of saying "this resource has changed address, but it's still the same thing in AWS (or your cloud provider)." It tells Terraform: "Don't destroy the old resource and create a new one; just update the state file to point to a new location."
The basic syntax is simple:
moved {
from = aws_instance.old_name
to = aws_instance.new_name
}When you run terraform apply after adding this block, Terraform will update its state to rename the resource reference from aws_instance.old_name to aws_instance.new_name. The actual EC2 instance in AWS doesn't change—only how Terraform refers to it.
This is especially critical in production environments. Imagine you have a database instance that took 30 minutes to initialize and contains live customer data. Without moved, renaming it would trigger a replacement, causing an outage. With moved, the rename happens instantly in your state file, and the database keeps running.
Safe module restructuring
Module reorganization is one of the most common refactoring patterns. You might pull resources out of a monolithic module into smaller, focused ones. Without moved blocks, this would destroy and recreate everything in that module.
Let's say you have a module legacy_app that manages both networking and compute:
module "legacy_app" {
source = "./modules/legacy_app"
environment = var.environment
}
resource "aws_instance" "app_server" {
# created by the module internally
}You decide to split this into network and compute modules. The current state has resources like module.legacy_app.aws_security_group.app and module.legacy_app.aws_instance.server. After the refactor, you'll have module.network.aws_security_group.app and module.compute.aws_instance.server.
Add moved blocks to map the old locations to the new ones:
moved {
from = module.legacy_app.aws_security_group.app
to = module.network.aws_security_group.app
}
moved {
from = module.legacy_app.aws_instance.server
to = module.compute.aws_instance.server
}
moved {
from = module.legacy_app.aws_subnet.main
to = module.network.aws_subnet.main
}When your team applies this Terraform, the resources stay alive. The load balancer keeps balancing traffic, the database keeps running, and there's zero downtime. Only your team's understanding of the infrastructure improves—the infrastructure itself is untouched.
In a multi-environment setup, you might have this scenario replicated across staging and production. A well-planned moved block strategy lets you apply the same refactor to both environments with confidence.
Reviewable in pull requests
moved blocks are one of the few Terraform constructs that are genuinely safe to review and merge without running a full terraform plan in production. Because they don't change any actual infrastructure—only the state file mapping—they're lower risk than resource changes.
However, reviewers should still understand what's happening. A good pull request message explains the intent:
Title: Refactor: reorganize networking and compute into separate modules
Description: This PR splits
legacy_appmodule intonetworkandcomputemodules for better maintainability. Includesmovedblocks to preserve all existing AWS resources. No infrastructure changes—state file updates only.Changes:
- New modules:
modules/network,modules/compute- Removed:
modules/legacy_app- Added 7
movedblocks to map old resource addresses to new onesTesting: Validated with
terraform plan -out=tfplanin staging. Plan shows state updates only, no resource replacements.
When reviewers see this, they understand: "This is a code organization change, not an infrastructure change." They can verify that every resource being moved has a corresponding moved block, and that there are no orphaned resources left behind.
In your CI/CD pipeline, you can enforce this with a step that checks for moved blocks whenever resources are removed. Some teams use a pre-commit hook or a custom policy-as-code rule to catch cases where a developer deletes a resource without adding a corresponding moved block.
Remove after everyone has applied
Here's the critical detail many teams miss: moved blocks should be temporary. Once everyone on your team has applied the refactored Terraform, the moved blocks are no longer needed. In fact, leaving them in indefinitely creates confusion and technical debt.
Here's the workflow:
- Day 1: Merge PR with refactored code and
movedblocks. - Day 1-3: Dev, staging, and production teams apply the new Terraform. Each apply runs the state migrations.
- Day 4+: Once everyone has applied and nothing broke, remove the
movedblocks in a follow-up PR.
Why remove them? Because they serve no purpose after the migration is complete. They're like scaffolding on a building—helpful during construction, but you tear it down when you're done. Leaving them in place makes the codebase harder to understand and can confuse future developers who wonder why those blocks exist.
A good practice: add a comment to the PR that includes moved blocks explaining when they can be removed.
# TEMPORARY: Remove these moved blocks after all environments have applied.
# They safely migrate resources from the old module structure to the new one.
# Created: 2024-01-15, can be removed after: 2024-01-18
moved {
from = module.legacy_app.aws_security_group.app
to = module.network.aws_security_group.app
}After the deadline, create a new PR that simply deletes all the moved blocks. The terraform plan will show no changes, confirming that the migration is complete and the blocks are safe to remove.
Common mistakes
Forgetting to move all related resources. If you move a security group but forget to move the associated IAM role, you'll end up with orphaned resources. Before removing moved blocks, run terraform state list in the old module path to ensure nothing was left behind.
Using moved blocks for actual refactoring instead of state updates. moved blocks don't change your code structure—they only update the state. If you're restructuring your modules, you still need to actually move the code files and update the module sources. The moved blocks come after the code changes.
Not testing in a lower environment first. Always apply a refactor with moved blocks in a development or staging environment first. Watch the plan output carefully. You should see no destroys and no creates, only state updates. If you see destroy/create in the plan, stop and investigate—something is wrong.
Removing moved blocks too early. If one team member hasn't applied yet and you remove the moved blocks, their next apply will try to destroy and recreate the resources. Communicate with your team and set a clear deadline.
Adding moved blocks for resources that don't exist in state. If you reference a resource that was never created or was already destroyed, the moved block has no effect. This isn't harmful, but it creates confusion. Only include moved blocks for resources that actually exist in your current state file.
Key takeaways
movedblocks let you rename and reorganize resources in your Terraform code without destroying the underlying infrastructure.- They're essential for safe module refactoring in production environments, preventing unintended downtime or data loss.
- Use the syntax
moved { from = old_address to = new_address }for each resource being migrated. - Module moves require one
movedblock per resource: list them explicitly rather than trying to move an entire module at once. movedblocks are temporary—remove them after all teams have applied the refactored Terraform.- Always test refactors with
movedblocks in a non-production environment first and verify that the plan shows no destroys. - Include clear documentation in pull requests explaining what's being moved and when the blocks can be safely removed.
- Communicate with your team about application timelines to ensure no one tries to apply before
movedblocks are in place.






Comments
Loading comments…