Terraform introduced the moved block in version 1.1.0. This block provides a straightforward way to refactor resources by explicitly mapping old resource addresses to new ones. It significantly reduces the risk of losing state or manually managing imports during renames or moves.
In this article, we’ll explain what the moved block is and how to use it to streamline resource restructuring for smoother and safer Terraform updates.
A Terraform moved block is used within a module’s moved section to declare the migration of a resource or data source from one address to another. It is helpful for refactoring Terraform code while ensuring that state information is preserved, preventing Terraform from destroying and recreating resources unnecessarily.
The moved block also handles resource renaming or movement across modules, making state management seamless.
The syntax of the Terraform moved block is as follows:
moved {
from = "<old_address>"
to = "<new_address>"
}
Where:
from
is the original address of the resource or module in the previous configurationto
is the new address of the resource or module in the updated configuration
The moved block in Terraform is used when you need to refactor or rename resources in your configuration without destroying and recreating them. It can be useful for:
- Renaming resources: When you change the name of a resource within the configuration (e.g.,
aws_instance.old_name
toaws_instance.new_name
). - Reorganizing modules: If a resource is moved between modules or changed from a root-level resource to a module-managed resource (or vice versa), you can use the moved block to keep Terraform aware of the transition.
- Refactoring module names: When you are renaming or reorganizing modules to align with new naming conventions or standards.
- Changing resource block types: If you change the type of a resource block (e.g., from
google_compute_instance
togoogle_compute_engine
), the moved block maps the old resource to the new type. Note: This is possible only if the configurations are compatible and the resource’s state remains valid. - Splitting or consolidating configurations: When you split a configuration into multiple files or modules or consolidate it into fewer files, use the moved block to reflect these changes without resource re-creation.
Let’s consider some examples:
Example 1: Renaming a resource
In this example, we will rename a Terraform resource from aws_instance.old_name
to aws_instance.new_name
.
# Old Configuration
# This block is no longer present
resource "aws_instance" "old_name" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
# New Configuration
resource "aws_instance" "new_name" {
ami = "ami-12345678"
instance_type = "t2.micro"
}
# Moved Block
moved {
from = "aws_instance.old_name"
to = "aws_instance.new_name"
}
Here, the moved block tells Terraform that the resource aws_instance.old_name
has been renamed to aws_instance.new_name
.
Note: After making these changes, make sure you run terraform plan
to verify that Terraform recognizes the migration correctly and that no unnecessary changes will be applied.
Example 2: Moving resources between modules
In the next example, a resource is moved from the root module to a nested module.
We want the aws_s3_bucket.my_bucket
resource to be moved from the root module to a module named storage
:
# Old Configuration (Root Module)
# This block is no longer present
resource "aws_s3_bucket" "my_bucket" {
bucket = "example-bucket"
}
# New Configuration (Nested Module)
# modules/storage/main.tf
resource "aws_s3_bucket" "my_bucket" {
bucket = "example-bucket"
}
# Root Module Configuration
module "storage" {
source = "./modules/storage"
}
# Moved Block
moved {
from = "aws_s3_bucket.my_bucket"
to = "module.storage.aws_s3_bucket.my_bucket"
}
# Old Configuration
# This block is no longer present
resource "aws_security_group" "old_group" {
for_each = toset(["web", "db"])
name = "sg-${each.key}"
}
# New Configuration
resource "aws_security_group" "new_group" {
for_each = toset(["web", "db"])
name = "sg-${each.key}"
}
# Moved Block
moved {
from = "aws_security_group.old_group[\"web\"]"
to = "aws_security_group.new_group[\"web\"]"
}
moved {
from = "aws_security_group.old_group[\"db\"]"
to = "aws_security_group.new_group[\"db\"]"
}
Each resource in the old_group is mapped to a corresponding resource in new_group
using moved blocks. This ensures a smooth state migration for all instances of the resources without unnecessary recreation.
Example 4: Moving a resource with a changed identifier
In this example, a resource’s identifier changes because the for_each
key expression is modified.
We want to rename the for_each
keys from ["app", "db"]
to ["frontend", "database"]
. Without the moved block, Terraform would see these as new resources and destroy the old ones.
# Old Configuration
resource "aws_security_group" "sg" {
for_each = toset(["app", "db"])
name = "sg-${each.key}"
}
# New Configuration
resource "aws_security_group" "sg" {
for_each = toset(["frontend", "database"]) # Changed identifiers
name = "sg-${each.key}"
}
# Moved Blocks
moved {
from = "aws_security_group.sg[\"app\"]"
to = "aws_security_group.sg[\"frontend\"]"
}
moved {
from = "aws_security_group.sg[\"db\"]"
to = "aws_security_group.sg[\"database\"]"
}
Each moved block maps the old key (app
or db
) to the new key (frontend
or database
).
When you run terraform plan
and terraform apply
, Terraform updates the state to match the new identifiers, avoiding unnecessary destruction and re-creation of the security groups.
Example 5: Moving a resource between providers
If we want to migrate a resource from one provider to another (e.g., from aws
to aws.other_region
), the Terraform moved block ensures a seamless state transition.
In our example, the S3 bucket resource is initially in the us-west-1
region using the default aws
provider. We want to manage the same bucket in a different AWS region (e.g., us-east-1
) using a different provider alias.
# Old Configuration
provider "aws" {
region = "us-west-1"
}
resource "aws_s3_bucket" "example_bucket" {
bucket = "example-bucket"
}
# New Configuration
provider "aws" {
region = "us-west-1"
}
provider "aws" {
alias = "other_region"
region = "us-east-1"
}
resource "aws_s3_bucket" "example_bucket" {
provider = aws.other_region
bucket = "example-bucket"
}
# Moved Block
moved {
from = "aws_s3_bucket.example_bucket"
to = "aws_s3_bucket.example_bucket"
}
The moved block ensures Terraform updates the state to associate the resource with the new provider configuration (aws.other_region
). The bucket itself remains intact in AWS and is not recreated, avoiding downtime or data loss.
The moved block in Terraform is useful for handling resource renaming or moving across modules in a safe and structured manner, but it has some limitations:
- Manual specification: The moved block requires you to manually specify both the source and destination addresses, which can lead to human error if the information is not provided accurately.
- Resource renames only: The moved block is specifically designed for renaming or moving resources within the state. Splitting or restructuring state might require manual state modifications or the use of tools like
terraform state mv
. - Static declaration: The moved block is static and does not support conditional logic. It cannot handle scenarios in which the movement depends on dynamic conditions.
- Limited to planned changes: The moved block is only applied during the
terraform plan
andapply
commands. If you modify the state file manually, the moved block will not help reconcile those changes. - Requires state compatibility: The source and destination must be part of the same state file. This is particularly important for users working with remote backends.
- Only supported in Terraform 1.1+: Older versions of Terraform do not recognize the moved block, making it incompatible with legacy configurations or environments.
Terraform is really powerful, but to achieve an end-to-end secure Gitops approach, you need to use a product that can run your Terraform workflows. Spacelift takes managing Terraform to the next level by giving you access to a powerful CI/CD workflow and unlocking features such as:
- Policies (based on Open Policy Agent) – You can control how many approvals you need for runs, what kind of resources you can create, and what kind of parameters these resources can have, and you can also control the behavior when a pull request is open or merged.
- Multi-IaC workflows – Combine Terraform with Kubernetes, Ansible, and other infrastructure-as-code (IaC) tools such as OpenTofu, Pulumi, and CloudFormation, create dependencies among them, and share outputs
- Build self-service infrastructure – You can use Blueprints to build self-service infrastructure; simply complete a form to provision infrastructure based on Terraform and other supported tools.
- Integrations with any third-party tools – You can integrate with your favorite third-party tools and even build policies for them. For example, see how to integrate security tools in your workflows using Custom Inputs.
Spacelift enables you to create private workers inside your infrastructure, which helps you execute Spacelift-related workflows on your end. Read the documentation for more information on configuring private workers.
You can check it out for free by creating a trial account or booking a demo with one of our engineers.
The moved block in Terraform is a practical tool for restructuring resources without disrupting infrastructure. It ensures seamless state updates, avoids downtime, and simplifies complex refactoring tasks, allowing you to reorganize code confidently and maintain operational stability — provided the mappings are accurate and proper planning is in place.
Note: New versions of Terraform are placed under the BUSL license, but everything created before version 1.5.x stays open-source. OpenTofu is an open-source version of Terraform that expands on Terraform’s existing concepts and offerings. It is a viable alternative to HashiCorp’s Terraform, being forked from Terraform version 1.5.6.
Manage Terraform Better and Faster
If you are struggling with Terraform automation and management, check out Spacelift. It helps you manage Terraform state, build more complex workflows, and adds several must-have capabilities for end-to-end infrastructure management.