Terraform’s moved block is the secret weapon for refactoring your infrastructure code without the dreaded terraform destroy and terraform apply cycle.
Let’s see it in action. Imagine you have a aws_instance resource and you decide to organize your Terraform code better by moving it into a separate module.
Original Code:
resource "aws_instance" "web_server" {
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
Refactored Code (after moving aws_instance to a module modules/compute):
// In main.tf
module "web" {
source = "./modules/compute"
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
// In modules/compute/main.tf
resource "aws_instance" "server" {
ami = var.ami
instance_type = var.instance_type
tags = var.tags
}
variable "ami" {}
variable "instance_type" {}
variable "tags" {}
If you just run terraform plan now, Terraform will see the old aws_instance.web_server as something to be destroyed and the new module.web.aws_instance.server as something to be created. That’s not what we want. We want Terraform to recognize that the existing instance is now being managed by the module.
This is where the moved block comes in. You add it to your root module (e.g., main.tf) to tell Terraform about the relocation.
// In main.tf (after refactoring)
resource "aws_instance" "web_server" {
// This block is now gone from the root module,
// but we need to tell Terraform where it went.
// This is a placeholder to satisfy the plan.
// The actual resource is now managed by the module.
lifecycle {
prevent_destroy = true
}
ami = "ami-0abcdef1234567890"
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
module "web" {
source = "./modules/compute"
ami = aws_instance.web_server.ami # Referencing the old resource's attributes
instance_type = aws_instance.web_server.instance_type
tags = aws_instance.web_server.tags
}
// The moved block tells Terraform about the refactoring
moved {
from = aws_instance.web_server
to = module.web.aws_instance.server
}
When you run terraform plan with the moved block, Terraform compares the from address to the to address. It sees that the resource previously known as aws_instance.web_server is now intended to be managed by module.web.aws_instance.server. Crucially, it understands that these refer to the same underlying infrastructure resource. The plan will then show no changes (or only changes to attributes that were legitimately modified, not because of the move itself).
The moved block is essentially a declaration that "this existing resource, identified by from, should now be managed by Terraform at the to address." It helps Terraform update its state file to reflect the new resource address without needing to destroy and recreate the actual infrastructure.
The from and to arguments can be resource addresses, module calls, or even data sources. You can move resources between modules, into modules, out of modules, or simply rename them.
For instance, if you just renamed a resource within the same file:
Before:
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-unique-bucket-name"
}
After renaming to data_bucket:
resource "aws_s3_bucket" "data_bucket" {
bucket = "my-unique-bucket-name"
}
moved {
from = aws_s3_bucket.my_bucket
to = aws_s3_bucket.data_bucket
}
Running terraform plan after this rename will correctly show no changes.
The moved block is designed to be used once per refactoring. After you’ve successfully run terraform plan and terraform apply with the moved block in place, you should remove it from your configuration. It’s a temporary bridge to guide Terraform through your code changes. Leaving moved blocks in your configuration indefinitely can lead to confusion and unexpected behavior in future plans.
The moved block is not just for resources; it can also be used to track changes in module sources or providers. For example, if you change the source of a module, you can use a moved block to inform Terraform about this shift.
The moved block is a declarative statement about the state of your infrastructure. It tells Terraform that the resource identified by from in the state file should now be considered managed by the resource identified by to in the configuration. This allows Terraform to reconcile the difference between the configuration and the state file without resorting to destructive operations.
The most surprising aspect of the moved block is that it doesn’t directly modify the state file itself; rather, it influences how terraform plan interprets the differences between your configuration and the current state. During terraform plan, Terraform reads the moved blocks and adjusts its understanding of resource addresses. If the plan is applied, Terraform then updates the state file to reflect the new addresses as specified in the to argument.
The next step after successfully refactoring with moved blocks is to understand how to manage resource dependencies when refactoring complex configurations.