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

➡️ Register Now

Terraform

Terraform State Mv Command: How to Move Resources

terraform state mv

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.

The terraform state mv command ensures smooth transitions when renaming resources, reorganizing configurations, or splitting state files. In this article, we explore this command in-depth, with step-by-step examples for handling single resources, modules, for_each scenarios, and cross-file migrations.

What is terraform state mv command?

terraform state mv is a Terraform command used to move items within the Terraform state. It allows you to rename resources or modules within the Terraform state file without recreating or destroying them, which is particularly useful when refactoring configurations or reorganizing Terraform resources.

terraform state mv [options] SOURCE DESTINATION
  • SOURCE: The current address of the resource, module, or instance in the state file (e.g., module.old.module.resource_name).
  • DESTINATION: The new address in the state file where you want the resource or module moved (e.g., module.new.resource_name).
  • [options]: Optional flags, such as:
    • -state: Path to the state file to use.
    • -state-out: Path to write the updated state file if different from the input state.
    • -lock: Enables or disables state file locking during the operation.
    • -lock-timeout=TIMEOUT: Specifies the duration to wait for a lock on the state file before failing.

The terraform state mv command only affects the Terraform state file because it does not change the actual infrastructure. However, improper use of the command can lead to mismatches between the state and the infrastructure. Always validate the changes after running this command.

What does terraform state mv do?

The terraform state mv command moves resources or modules from one location in Terraform’s state file to another. Here are some scenarios when it can be useful:

  • Renaming resources: When you change the name of a resource in your configuration file, you can use terraform state mv to update the state file to reflect the new name, avoiding resource destruction and recreation.
  • Reorganizing resources: If you move resources into modules or between modules, terraform state mv allows you to keep the state in sync with your new configuration structure.
  • Fixing state issues: terraform state mv helps correct discrepancies between your Terraform configuration and the state.

What is the difference between terraform state mv and terraform import?

The difference between terraform state mv and terraform import lies in their purposes and how they interact with Terraform state. terraform state mv is used to move an existing resource within your Terraform state, and the terraform import command is used to import an existing resource that was created outside of Terraform into your Terraform state. 

In short, terraform state mv rearranges existing resources within the state, while terraform import brings external resources under Terraform’s management.

How to use terraform state mv

Let’s see some examples.

Example 1: Moving a single resource

Let’s suppose we initially created an AWS S3 bucket called aws_s3_bucket.my_bucket in your Terraform configuration. Later, we decide to rename the resource to aws_s3_bucket.main_bucket

After updating the configuration, the Terraform state still retains the old name (aws_s3_bucket.my_bucket), and if the state is not updated Terraform will try to recreate the bucket with the new name.

To avoid this, we would run:

terraform state mv aws_s3_bucket.my_bucket aws_s3_bucket.main_bucket

Terraform will no longer attempt to recreate the resource, as the state reflects the new configuration.

Example 2: Moving multiple resources

In this example, we have multiple AWS EC2 instances in our root module:

resource "aws_instance" "web1" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}

resource "aws_instance" "web2" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}

resource "aws_instance" "web3" {
  ami           = "ami-12345678"
  instance_type = "t2.micro"
}

We want to move from the root module to a nested module (module.instances). If the resources are updated in the configuration to be within the module, the Terraform state needs to reflect this change.

# Updated Configuration
module "instances" {
  source = "./modules/instances"

  instances = [
    {
      ami           = "ami-12345678"
      instance_type = "t2.micro"
    },
    {
      ami           = "ami-12345678"
      instance_type = "t2.micro"
    },
    {
      ami           = "ami-12345678"
      instance_type = "t2.micro"
    },
  ]
}

Instead of moving each resource individually, we can use the terraform state mv command for each one:

terraform state mv aws_instance.web1 module.instances.aws_instance.web1

terraform state mv aws_instance.web2 module.instances.aws_instance.web2

terraform state mv aws_instance.web3 module.instances.aws_instance.web3

Note: There is no single bulk operation for moving multiple resources at once. 

Example 3: Moving resources between state files

Let’s consider a scenario where we want to split your Terraform configuration into separate state files, such as when moving resources from one state file (state1.tfstate) to another (state2.tfstate).

Suppose you have an AWS RDS instance defined as follows in state1.tfstate:

resource "aws_db_instance" "database" {
  allocated_storage    = 20
  engine               = "mysql"
  instance_class       = "db.t2.micro"
  name                 = "mydatabase"
  username             = "admin"
  password             = "password123"
}

We want to move this resource to another state file (state2.tfstate) for better state management. We will run the following command:

terraform state mv -state-out=state2.tfstate aws_db_instance.database

The -state-out flag specifies the target state file (state2.tfstate) to which the resource will be moved. Terraform updates state2.tfstate to include the resource and removes it from state1.tfstate.

Always validate the changes by inspecting both state files using terraform show to ensure the resource was transferred correctly.

Example 4: Moving a resource into a module

In this example, we defined an AWS IAM role in the root module as aws_iam_role.my_role:

resource "aws_iam_role" "my_role" {
  name               = "my-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Action = "sts:AssumeRole"
      Effect = "Allow"
      Principal = {
        Service = "ec2.amazonaws.com"
      }
    }]
  })
}

Now we want to organize your configuration by moving the role into a new module called roles

module "roles" {
  source = "./modules/roles"

  iam_roles = [
    {
      name               = "my-role"
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [{
          Action = "sts:AssumeRole"
          Effect = "Allow"
          Principal = {
            Service = "ec2.amazonaws.com"
          }
        }]
      })
    }
  ]
}

After updating our configuration to place the resource within the module, the Terraform state must be updated to reflect the new structure.

terraform state mv aws_iam_role.my_role module.roles.aws_iam_role.my_role

Terraform now recognizes the IAM role within the roles module and won’t attempt to recreate it.

Example 5: Renaming a resource across modules

Let’s suppose you have a resource module.network.aws_vpc.main in a module named network. We want to rename this VPC to module.network.aws_vpc.primary within the same module.

module "network" {
  source = "./modules/network"

  vpc_name = "main-vpc"
}

module "network" {
  source = "./modules/network"

  vpc_name = "primary-vpc"
}

The terraform state mv command updates the state to recognize the VPC under its new name, avoiding recreation of the resource.

terraform state mv module.network.aws_vpc.main module.network.aws_vpc.primary

Example 6: Moving a resource created with for_each

Let’s define multiple AWS S3 buckets using Terraform for_each in the root module:

variable "bucket_names" {
  default = ["bucket1", "bucket2", "bucket3"]
}

resource "aws_s3_bucket" "buckets" {
  for_each = toset(var.bucket_names)

  bucket = each.key
}

We now want to refactor our configuration and move them into a new module called buckets.

module "buckets" {
  source       = "./modules/buckets"
  bucket_names = ["bucket1", "bucket2", "bucket3"]
}

modules/buckets/main.tf

variable "bucket_names" {
  type = list(string)
}

resource "aws_s3_bucket" "buckets" {
  for_each = toset(var.bucket_names)

  bucket = each.key
}

For each bucket, you need to move its state individually:

terraform state mv aws_s3_bucket.buckets["bucket1"] module.buckets.aws_s3_bucket.buckets["bucket1"]

terraform state mv aws_s3_bucket.buckets["bucket2"] module.buckets.aws_s3_bucket.buckets["bucket2"]

terraform state mv aws_s3_bucket.buckets["bucket3"] module.buckets.aws_s3_bucket.buckets

Ensure that each source and destination address is complete and correct to avoid state mismatch or duplication.

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 for free by creating a trial account or booking a demo with one of our engineers.

Key points

The terraform state mv command enables precise state management by relocating resources within or across modules and state files. It prevents resource recreation during refactoring or renaming, maintaining alignment between the state and configuration.

Remember that improper use of terraform state mv can result in state drift or resource duplication, so always back up your state files, validate the changes, and test the configuration after running the command.

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