Stage 4 · Provision
Advanced Terraform Patterns
Importing Existing Infrastructure
Terraform import, import blocks, moved blocks, and refactoring strategies — bringing legacy resources under Terraform management.
Why Import?
Existing infrastructure created manually or by other tools can be brought under Terraform management. This process is called importing. Once imported, Terraform tracks the resource in state and can modify or destroy it through code.
Importing is essential for organizations transitioning to Terraform. You cannot rewrite your entire infrastructure from scratch. The incremental approach — import existing resources, then modify them with code — is the practical path.
terraform import
The terraform import command maps a real-world resource to a Terraform resource address. It does not write configuration — you must write the resource block first, then run import to associate it with the real resource.
$ terraform import aws_instance.web i-0123456789abcdef0
$ terraform import aws_vpc.main vpc-0123456789abcdef0
$ terraform import 'aws_security_group.web["allow_http"]' sg-0123456789abcdef0The import command takes the Terraform resource address and the real resource ID. For resources using for_each, include the key in quotes. After import, run terraform plan to verify the resource matches your configuration.
$ terraform import module.vpc.aws_vpc.main vpc-0123456789abcdef0
$ terraform import 'module.ec2.aws_instance.web["web-1"]' i-0123456789abcdef0Module resources use the module prefix. The full address must match the resource definition in your configuration. If the address does not exist in the configuration, Terraform will error.
Import Blocks
Import blocks are a declarative alternative to the terraform import command. They are written in HCL and evaluated during terraform plan. This allows code review and CI validation of imports.
import {
to = aws_instance.web
id = "i-0123456789abcdef0"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "imported-web-server"
}
}The import block tells Terraform to import the resource during plan. The resource block must exist with the correct configuration. Terraform verifies that the imported resource matches the configuration before applying.
import {
for_each = toset(["i-abc123", "i-def456"])
to = aws_instance.web[each.key]
id = each.value
}
resource "aws_instance" "web" {
for_each = toset(["i-abc123", "i-def456"])
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
}The for_each import block imports multiple resources in one declaration. Each key in the toset maps to a resource instance and a real resource ID. This is cleaner than running terraform import multiple times.
Moved Blocks
Moved blocks tell Terraform to rename or reorganize resources without destroying and recreating them. They are essential for refactoring — changing resource addresses, moving resources into modules, or updating for_each keys.
moved {
from = aws_instance.web_server
to = aws_instance.web
}When Terraform encounters this moved block, it renames the resource in state without destroying it. The old address is removed from state and the new address is added. No real infrastructure changes occur.
moved {
from = aws_instance.web
to = module.compute.aws_instance.web
}
moved {
from = aws_vpc.main
to = module.networking.aws_vpc.main
}This moves a resource from the root module into a child module. Terraform updates the state file to reflect the new address. The resource is not recreated — only the state reference changes.
Refactoring Strategies
Refactoring Terraform requires careful planning. The goal is to change the code structure without modifying real infrastructure. Moved blocks, import blocks, and state commands are your tools.
$ terraform state mv aws_instance.web aws_instance.web_server
$ terraform state mv 'aws_instance.web["a"]' 'aws_instance.web["primary"]'
$ terraform state mv module.old_name module.new_name
$ terraform state rm aws_instance.legacyState commands modify the state file directly. Use them for complex refactors that moved blocks cannot handle. Always backup the state file before running state commands.
Common Pitfalls
- Import does not write configuration — you must write the resource block first.
- Imported resources often show changes on the first plan — attributes may not match your configuration.
- State commands modify state directly — always backup before running them.
- Moved blocks only work during the plan/apply cycle — they are removed from the configuration after applying.
- Import blocks require the resource to exist in configuration — you cannot import without a corresponding resource block.
After importing a resource, run terraform plan to see what Terraform wants to change. If there are unexpected changes, update your configuration to match the actual resource state before applying.
Import blocks and terraform import commands are evaluated once. After the resource is in state, the import block is no longer needed. Remove it from your configuration after the first successful apply.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.