You have infrastructure created manually or with CloudFormation. Terraform import brings it under IaC management without recreating anything.
terraform import (Classic)
# Write the resource block first
# main.tf
resource "aws_instance" "web" {
# Will be filled in after import
}
# Import
terraform import aws_instance.web i-1234567890abcdef0
# Now run plan to see what needs to match
terraform planThe plan shows drift between your config and the real resource. Update your .tf file until plan shows no changes.
Import Blocks (Terraform 1.5+)
Declarative imports — better for teams:
# imports.tf
import {
to = aws_instance.web
id = "i-1234567890abcdef0"
}
import {
to = aws_security_group.web
id = "sg-0123456789abcdef0"
}
import {
to = aws_vpc.main
id = "vpc-0123456789abcdef0"
}# Generate config automatically (Terraform 1.5+)
terraform plan -generate-config-out=generated.tf
# Review generated.tf, clean it up, move to proper files
# Then run
terraform applyAfter apply, remove the import blocks — they are one-time use.
Generated Config Example
terraform plan -generate-config-out=generated.tfOutput in generated.tf:
resource "aws_instance" "web" {
ami = "ami-0c1c30571d2dae5c9"
instance_type = "t3.micro"
key_name = "my-key"
subnet_id = "subnet-abc123"
vpc_security_group_ids = ["sg-def456"]
tags = {
Name = "web-server"
}
# ... many more attributes
}Clean up: 1. Remove computed/read-only attributes 2. Replace hardcoded IDs with references or variables 3. Move to appropriate files
Master this topic with hands-on labs
Go beyond reading — build real projects in sandboxed environments with expert video guidance.
Browse Courses →Step-by-Step Migration
1. Inventory Existing Resources
# AWS
aws ec2 describe-instances --query 'Reservations[].Instances[].[InstanceId,Tags]'
aws rds describe-db-instances --query 'DBInstances[].[DBInstanceIdentifier]'
aws s3 ls
# Or use terraformer (auto-generates tf + state)
terraformer import aws --resources=ec2_instance,s3 --regions=eu-west-12. Write Import Blocks
# imports.tf
import {
to = aws_vpc.main
id = "vpc-abc123"
}
import {
to = aws_subnet.public["eu-west-1a"]
id = "subnet-def456"
}
import {
to = aws_subnet.public["eu-west-1b"]
id = "subnet-ghi789"
}
import {
to = aws_db_instance.main
id = "production-db"
}3. Generate and Clean Config
terraform plan -generate-config-out=generated.tf4. Refactor
# Before (generated — hardcoded)
resource "aws_instance" "web" {
ami = "ami-0c1c30571d2dae5c9"
subnet_id = "subnet-def456"
instance_type = "t3.micro"
}
# After (clean — using references and variables)
resource "aws_instance" "web" {
ami = var.ami_id
subnet_id = aws_subnet.public["eu-west-1a"].id
instance_type = var.instance_type
tags = {
Name = "${var.project}-web"
Environment = var.environment
}
}5. Verify Zero Changes
terraform plan
# Should show: No changes. Your infrastructure matches the configuration.Import Common Resources
# VPC
import { to = aws_vpc.main; id = "vpc-abc123" }
# Subnet
import { to = aws_subnet.web; id = "subnet-def456" }
# Security Group
import { to = aws_security_group.web; id = "sg-ghi789" }
# EC2 Instance
import { to = aws_instance.web; id = "i-jkl012" }
# RDS
import { to = aws_db_instance.main; id = "my-database" }
# S3 Bucket
import { to = aws_s3_bucket.data; id = "my-bucket-name" }
# IAM Role
import { to = aws_iam_role.app; id = "my-role-name" }
# Route53 Record
import { to = aws_route53_record.www; id = "Z1234_www.example.com_A" }
# ALB
import { to = aws_lb.main; id = "arn:aws:elasticloadbalancing:..." }Handling for_each Imports
locals {
subnets = {
"eu-west-1a" = "subnet-abc123"
"eu-west-1b" = "subnet-def456"
}
}
import {
for_each = local.subnets
to = aws_subnet.public[each.key]
id = each.value
}Get weekly IT automation tips
Docker, Ansible, Terraform, MLOps — curated insights delivered to your inbox. No spam.
Subscribe Free →Common Pitfalls
| Issue | Solution |
|---|---|
| Plan shows changes after import | Update config to match real resource exactly |
| Sensitive values missing | Set in terraform.tfvars or mark as sensitive |
| Resource not found | Check region, account, resource ID format |
| State conflict | terraform state rm the duplicate first |
| Partial import | Import dependent resources together (VPC before subnets) |
State Surgery
# View state
terraform state list
terraform state show aws_instance.web
# Move resource (rename)
terraform state mv aws_instance.old aws_instance.new
# Remove from state (without destroying)
terraform state rm aws_instance.web
# Pull state
terraform state pull > state.jsonWhat's Next?
Our Terraform for Beginners course covers infrastructure import and state management across 15 hands-on lessons. First lesson is free.
---
Ready to go deeper? Check out our hands-on course: Terraform for Beginners — practical exercises you can follow along on your own machine.
Ready to learn by doing?
Stop reading tutorials — start building. Expert video courses with hands-on labs in real sandboxed environments.
Related Articles
GitHub Actions CI/CD for Terraform
Automate Terraform with GitHub Actions. Plan on PR, apply on merge, remote state locking, and secure secrets for IaC pipelines.
Terraform Workspaces for Environments
Use Terraform workspaces for dev, staging, and production. Practical patterns, trade-offs, and best practices for multi-env IaC.
Terraform Ansible Kubernetes Stack
Combine Terraform, Ansible, and Kubernetes for complete DevOps infrastructure. Provision, configure, and deploy end to end.
Terraform Lifecycle Rules
Master Terraform lifecycle rules — prevent_destroy, create_before_destroy, ignore_changes, and replace_triggered_by for safe infrastructure updates.
Terraform Loops and Conditionals
Terraform loops and conditionals. count, for_each, for expressions, dynamic blocks, and advanced patterns for DRY config code.
Terraform Modules Best Practices
Write reusable Terraform modules. Structure, variables, outputs, semantic versioning, automated testing, and registry publishing.
Explore topics
Browse more articles on the topics covered here.