IBM Support

Migrating Resources into a Module in Terraform

How To


Steps

Introduction

When refactoring Terraform configurations, it is common to move existing resources from a root module into a dedicated child module for better organization and reusability. However, if this change is not communicated to Terraform correctly, it may interpret the move as a request to destroy the old resource and create a new one, which can cause downtime or data loss.

Terraform tracks resources in its state file by their address. When you move a resource into a module, its address changes from aws_s3_bucket.logs to module.logs_bucket.aws_s3_bucket.this. Without explicit instruction, Terraform sees this as deleting one resource and adding another.

This guide demonstrates how to use the moved block to safely refactor resources into modules, ensuring Terraform understands the resource was relocated, not replaced.

Expected Outcome

You will successfully move a resource from your root configuration into a module and update the Terraform state to reflect the new resource address without destroying and recreating the underlying infrastructure.

Procedure

Follow these steps to migrate a resource into a module without causing resource recreation.

  1. Identify the Current Resource

    Your configuration starts with a resource defined in the root module. For this example, it is an S3 bucket in main.tf.

    # root/main.tf
    resource "aws_s3_bucket" "logs" {
      bucket = "acme-logs-prod"
    }
  2. Create the New Module

    Create a directory for your new module and define the resource within it. Use a variable to pass in the bucket name.

    # modules/s3_bucket/main.tf
    variable "name" { 
      type = string 
    }
    
    resource "aws_s3_bucket" "this" {
      bucket = var.name
      # ...rest of your config...
    }
  3. Reference the Module and Remove the Original Block

    In your root main.tf, remove the original resource block and add a module block that references your new module.

    # root/main.tf
    module "logs_bucket" {
      source = "./modules/s3_bucket"
      name   = "acme-logs-prod"
    }
  4. Add a moved Block

    In your root module, create a new file such as moved.tf and add a moved block. This block maps the resource's old state address to its new one.

    # root/moved.tf
    moved {
      from = aws_s3_bucket.logs
      to   = module.logs_bucket.aws_s3_bucket.this
    }
  5. Apply the Changes

    Run terraform plan to confirm that Terraform intends to move the resource in the state file rather than destroying and creating a new one. Then, apply the change.

    $ terraform init
    
    $ terraform plan
    ## The plan should show a "moved" action, not create/destroy actions.
    
    $ terraform apply

    Terraform updates the state reference with no changes to the deployed infrastructure.

Additional Information

Handling for_each and count

For resources created with for_each or count, you must include the instance key or index in the moved block addresses.

moved {
  from = aws_s3_bucket.logs["prod"]
  to   = module.logs_bucket.aws_s3_bucket.this["prod"]
}

Handling Multiple Resources

If a module refactoring splits a single legacy resource into multiple new resources (a common pattern in provider updates), add a separate moved block for each new resource.

moved {
  from = aws_s3_bucket_public_access_block.logs
  to   = module.logs_bucket.aws_s3_bucket_public_access_block.this
}

moved {
  from = aws_s3_bucket_versioning.logs
  to   = module.logs_bucket.aws_s3_bucket_versioning.this
}

Best Practices for moved Blocks

It is a good practice to keep moved blocks in your codebase for a period after the migration. This ensures that team members or CI/CD pipelines running older versions of the configuration do not generate incorrect plans. You can remove the blocks once the state transition is complete across all environments and branches.

For more details on refactoring, refer to the official documentation on moved blocks.

Document Location

Worldwide

[{"Type":"MASTER","Line of Business":{"code":"LOB77","label":"Automation Platform"},"Business Unit":{"code":"BU048","label":"IBM Software"},"Product":{"code":"SSGH5YK","label":"IBM Terraform Self-Managed"},"ARM Category":[{"code":"","label":""}],"ARM Case Number":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"All Version(s)"}]

Historical Number

44798389186195

Document Information

Modified date:
16 March 2026

UID

ibm17266071