Move block

As your Terraform configuration grows, there will likely be a need to refactor modules and resources. For example you might want to take a single module and break it into two separate modules, and move some resources into the new module. By default, Terraform thinks you want to delete the old resource and create a new one, when you rename or move it. That's because Terraform manages resources based on their unique addresses within the configuration. To avoid this, one can leverage "moved blocks" within a Terraform configuration. These blocks notify Terraform that a resource has intentionally been moved or renamed and that it should update the address of the resource instead of deleting and re-creating it.
When Terraform hit a moved block, it will look in the statefile for an already present object at the from address. If one is found, Terraform renames that object to the to address and then proceeds creating a plan as if the object had always been at the address it was just renamed to. This prevents unnecessary destructions and recreations of resources during apply.
NOTE: If the from address resource doesn't exist in the statefile, Terraform will destroy the from address resource and then create a new resource using the to address.

Moved Block Syntax

The syntax of a move block is as follows:
moved {
    from = <source-resource-address>
    to = <target-resource-address>
}
Here is the breakdown of syntax:

When to use a Move block?

Use a Move block in the following situations:

Scenario 1: Renaming a Resource

When renaming a resource in Terraform, you explicitly need to tell Terraform about this change, otherwise, it will try to recreate the resource. To archive the same, you provide the moved block. Let's assume you have a module with a resource configuration like :
// File: main.tf (Root Module)
resource "aws_instance" "server" {
    count = 2
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
When you apply this configuration, Terraform creates two instances: aws_instance.server[0] and aws_instance.server[1]. An entry is also created in the statefile. Now, to rename your resource to renamed-server, you will have to update your configuration as below :
// File: main.tf (Root Module)
resource "aws_instance" "renamed-server" {
    count = 2
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
moved {
    from = "aws_instance.server"
    to = "aws_instance.renamed-server"
}
When you created a new plan, Terraform considers the existing objects of aws_instance.server as if they were created for aws_instance.renamed-server. That is aws_instance.server[0] is now treated as aws_instance.renamed-server[1] and aws_instance.server[1] is now treated as aws_instance.renamed-server[1].
Terraform will perform the following actions:
    # aws_instance.server[0] has moved to aws_instance.renamed-server[0]
    resource "aws_instance" "renamed-server" {
        ami = ami-0123456789
        instance_type = t2.micro
    }
    # aws_instance.server[1] has moved to aws_instance.renamed-server[1]
    resource "aws_instance" "renamed-server" {
        ami = ami-0123456789
        instance_type = t2.micro
    }
Plan: 0 to add, 0 to change, 0 to destroy.
NOTE: You cannot use the moved block to change a managed resource (a resource block, e.g. resource "aws_instance") into a data resource (a data block, e.g. data "aws_instance"). These are fundamentally different and cannot be converted.

Scenario 2: Enabling count or for_each for a Resource

When you want to enable count or for_each for an already existing resource, Terraform needs to know how to handle such existing resources. You use the moved block to let Terraform know how it should treat the already existing resource.
Initially, you have a single-instance resource:
// File: main.tf (Root Module)
resource "aws_instance" "server" {
    ami = "ami-abc123"
    instance_type = "t2.micro"
}
Later on, you would want to use for_each to create several instances, and do not want to destroy the existing resource from infrastructure. You add a moved block in mapping the existing resource to one specific instance. When you use moved with instance keys, like ["t2.micro"], Terraform knows that you are referring to a specific instance of a resource, not the resource as a whole.
// File: main.tf (Root Module)
resource "aws_instance" "server" {
    for_each = tomap ({
        "t2.micro" = {
            "ami" = "ami-abc123"
            "instance_type" = "t2-micro"
        },
        "t2.small" = {
            "ami" = "ami-def456"
            "instance_type" = "t2-small"
        }
    })
    ami = each.value.ami
    instance_type = each.value.instance_type
}
moved {
    from = "aws_instance.server"
    to = "aws_instance.server["t2.micro"]"
}
The moved block instructs Terraform to treat the existing resource at aws_instance.server as if it initially was created as aws_instance.server["t2.micro"]. This prevents Terraform from making a plan to destroy the existing resource.
Terraform will perform the following actions:
    # aws_instance.server has moved to aws_instance.server["t2.micro"]
    resource "aws_instance" "server" {
        ami = ami-abc123
        instance_type = t2.micro
    }
    # aws_instance.server["t2.small"] will be created
    + resource "aws_instance" "server" {
        + ami = ami-def456
        + instance_type = t2.small
    + }
Plan: 1 to add, 0 to change, 0 to destroy.

Scenario 3: Renaming a Module Call

You can also rename a module call similar to how you would rename a resource with Terraform. If you rename a module call without informing Terraform of the change, Terraform will attempt to recreate the module's resource. Use a moved block to guide Terraform to the change. Suppose you have a module and its resource configuration is something like:
// File: instance/main.tf (Child Module)
resource "aws_instance" "server" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
    tags = {
        Name = "Dev-Ec2-instance"
    }
}
// File: main.tf (Root Module)
module "server" {
    source = "./instance/main.tf"
}
When you run this configuration, Terraform will create an instance of module that is module.server and entry created in statefile. In order to rename a module call, you will update the module block to use the new name as well as add a moved block for Terraform to know about the change.
// File: main.tf (Root Module)
module "renamed-server-module" {
    source = "./instance/main.tf"
}
moved {
    from = "module.server"
    to = "module.renamed-server-module"
}
When the next plan is being built, or the configuration is applied, Terraform will detect the rename and any resources previously created by the "server" module call will be treated as if it had been created by the new "renamed-server-module" module call.
Terraform will perform the following actions:
    # module.server.aws_instance.server has moved to module.renamed-server-module.aws_instance.server
    resource "aws_instance" "server" {
        ami = ami-abc123
        instance_type = t2.micro
    }
Plan: 0 to add, 0 to change, 0 to destroy.

Scenario 4: Enabling count or for_each for a Module Call

When you want to enable count or for_each for a module that already exists. Terraform really needs to know how to take over existing modules. You use the moved block for telling Terraform how it should treat the existing module.
Suppose you have a module call that creates only one instance.
// File: instance/main.tf (Child Module)
resource "aws_instance" "server" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
// File: main.tf (Root Module)
module "server" {
    source = "./instance/main.tf"
}
When you implement this configuration, Terraform will create resources whose addresses start with module.server. You can use the count later to create more instances of the module. A moved block can be used to specify which instance key the resource takes in the new configuration, without deleting the existing object that was previously associated with aws_instance.server.
// File: instance/main.tf (Child Module)
resource "aws_instance" "server" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
variable "index" {
    type = string
}
// File: main.tf (Root Module)
module "server" {
    count = 2
    source = "./instance/main.tf"
    index = "{count.index}"
}
moved {
    from = "module.server"
    to = "module.server[0]"
}
In this updated configuration, The module call now uses count = 2 to create two instances and moved block specifies that the existing object previously associated with module.server should be treated as if it was created in module.server[0].
Terraform will perform the following actions:
    # module.server.aws_instance.server[0] will be created
    + resource "aws_instance" "server" {
        + ami = ami-def456
        + instance_type = t2.small
    + }
    # module.server.aws_instance.server has moved to module.server.aws_instance.server[1]
    resource "aws_instance" "server" {
        ami = ami-abc123
        instance_type = t2.micro
    }
Plan: 1 to add, 0 to change, 0 to destroy.

Scenario 5: Splitting a Module into Multiple Modules

When a Terraform module has grown too large, it is time to refactor it into smaller modules. This practice is called Module Refactoring. Suppose we have a root module having three resources: an EC2 instance, an S3 bucket, and an RDS database instance.
// File: main.tf (Root Module)
resource "aws_instance" "server" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
resource "aws_s3_bucket" "buckets" {
    bucket = "demo"
    region = "us-east-1"
}
resource "aws_db_instance" "database" {
    allocated_storage = 9
    engine = "mysql"
    instance_class = "db.t2.micro"
    db_name = "database-1"
    username = "myuser"
    password = "mypassword"
}
When you run this configuration, Terraform will build three resources and creates an entry in the statefile. Later, if you decide to split this root module into separate modules, you need to inform Terraform about the split using moved block in the root module.
// File: dev-server/main.tf (Child Module)
resource "aws_instance" "server" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
// File: dev-s3/main.tf (Child Module)
resource "aws_s3_bucket" "buckets" {
    bucket = "demo"
    region = "us-east-1"
}
// File: dev-database/main.tf (Child Module)
resource "aws_db_instance" "database" {
    allocated_storage = 9
    engine = "mysql"
    instance_class = "db.t2.micro"
    db_name = "database-1"
    username = "myuser"
    password = "mypassword"
}
// File: main.tf (Root Module)
module "dev-server" {
    source = "./dev-server"
}
module "dev-s3" {
    source = "./dev-s3"
}
module "dev-database" {
    source = "./dev-database"
}
moved {
    from = "aws_instance.server"
    to = "module.dev-server.aws_instance.server"
}
moved {
    from = "aws_s3_bucket.buckets"
    to = "module.dev-s3.aws_s3_bucket.buckets"
}
moved {
    from = "aws_db_instance.default"
    to = "module.dev-database.aws_db_instance.default"
}
In the above example, we created three child modules: dev-server, dev-s3, and dev-database, each containing the respective resource. We added the three child modules in the root module and used the moved block in informing Terraform about the new locations for resources. Terraform will understand that the resources are refactored into new modules and hence won't try to replace them.
Terraform will perform the following actions:
    # aws_instance.server has moved to module.dev-server.aws_instance.server
    resource "aws_instance" "server" {
        ami = ami-abc123
        instance_type = t2.micro
    }
    # aws_s3_bucket.buckets has moved to module.dev-s3.aws_s3_bucket.buckets
    resource "aws_instance" "server" {
        bucket = demo
        region = us-east-1
    }
    # aws_db_instance.default has moved to module.dev-database.aws_db_instance.default
    resource "aws_db_instance" "default" {
        allocated_storage = 9
        db_name = proddb
        engine = mysql
        engine_version = 8.0
        username = admin
        password = password
    }
Plan: 0 to add, 0 to change, 0 to destroy.
When using count or for_each to create multiple instances of a resource within a module, you must specify a specific instance key when referencing those resources within a moved block.
Now, assume that you have a root module main.tf provisioned with multiples instances of an AWS EC2 instance aws_instance.server using count:
// File: main.tf (Root Module)
resource "aws_instance" "server" {
    count = 2
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
You want to move these resources into a submodule sub-module/main.tf. To do this, you must specify the instance key in moved block.
// File: sub-module/main.tf (Child Module)
resource "aws_instance" "server" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
// File: main.tf (Root Module)
module "dev-server" {
    count = 2
    source = "./sub-module"
}
moved {
    from = "aws_instance.server[0]"
    to = "module.sub-module[0].aws_instance.server"
}
moved {
    from = "aws_instance.server[1]"
    to = "module.sub-module[1].aws_instance.server"
}
In this example, you specify the instance key aws_instance.server[0] and aws_instance.server[1] to refer to the first and second instances of the module, respectively. This allows Terraform to correctly move the resources to the new location in the sub-module.

Related Pages

Feedback

Was this page helpful?