[Webinar] How to Boost Developer Productivity with Policy-Driven IaC

➡️ Register Now

Terraform

Using Terraform Moved Block to Refactor Resources

terraform moved block

Subscribe to our Newsletter

Mission Infrastructure newsletter is a monthly digest of the latest posts from our blog, curated to give you the insights you need to advance your infrastructure game.

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.

  1. What is the Terraform moved block?
  2. When to use the Terraform moved block?
  3. How to use the Terraform moved block
  4. Terraform moved block limitations

What is the Terraform moved block?

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 configuration
  • to is the new address of the resource or module in the updated configuration

When to use the Terraform moved block?

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 to aws_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 to google_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.

How to use the Terraform moved block

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"
}

The moved block updates the Terraform state to reflect the new location without destroying and recreating the resource.

Example 3: Using moved with for_each

We will now move a resource to a new name using a for_each loop, which creates multiple resources dynamically. 

# 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.

Terraform moved block limitations

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 and apply 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.

How to manage Terraform resources with Spacelift

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.

Key points

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.

Learn more

The Practitioner’s Guide to Scaling Infrastructure as Code

Transform your IaC management to scale

securely, efficiently, and productively

into the future.

ebook global banner
Share your data and download the guide