Day 25: Terraform Import In AWS

The Legacy Infrastructure Challenge
Many organizations have AWS resources created manually—legacy applications, quick prototypes, or emergency fixes. When transitioning to Infrastructure as Code (IaC) with Terraform, you face a critical problem: Terraform doesn't know these resources exist. If you write configuration and run terraform apply, it attempts to create duplicates, resulting in errors.
Refer to the Notes: Click Here
Refer to the Code: Click Here
Understanding Terraform State: The Real Problem
Terraform's power comes from its state file (terraform.tfstate). This JSON file acts as Terraform's memory:
When you run terraform plan, Terraform:
Reads desired state from
.tffilesCompares with actual state from
tfstateCalculates differences
Proposes changes
Critical Insight: No entry in tfstate = Resource doesn't exist (in Terraform's view).
The Import Solution: A Practical Walkthrough
Step 1: Manual AWS Resource
First, create resources manually in AWS Console:
Security Group:
app-security-groupwith SSH(22), HTTP(80), HTTPS(443) accessEC2 Instance:
t2.microwith this security group attachedNote IDs:
sg-0c398cb65a93047f2(SG) andi-1234567890abcdef0(EC2)
Step 2: Write Terraform Configuration
Create matching Terraform code:
# provider.tf
provider "aws" {
region = "us-east-1"
}
# data.tf
data "aws_vpc" "default" {
default = true
}
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"] # Canonical
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
}
}
# security-group.tf
resource "aws_security_group" "app_sg" {
name = "app-security-group"
description = "Security group for application servers"
vpc_id = data.aws_vpc.default.id
ingress {
description = "SSH access"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTP access"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS access"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# ec2-instance.tf
resource "aws_instance" "app_server" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
security_groups = [aws_security_group.app_sg.name]
tags = {
Name = "app-server"
}
}
Step 3: The Inevitable Failure
Run terraform plan and you'll see:
+ aws_security_group.app_sg
+ aws_instance.app_server
Run terraform apply and get:
Error: Error creating Security Group: InvalidGroup.Duplicate
│ The security group 'app-security-group' already exists.
Step 4: Import to the Rescue
Import each resource using its AWS identifier:
# Import security group
terraform import aws_security_group.app_sg sg-0c398cb65a93047f2
# Import EC2 instance
terraform import aws_instance.app_server i-1234567890abcdef0
Success Output:
aws_security_group.app_sg: Importing from ID "sg-0c398cb65a93047f2"...
aws_security_group.app_sg: Import prepared!
aws_security_group.app_sg: Refreshing state... [id=sg-0c398cb65a93047f2]
Import successful!
Step 5: Verify and Manage
Check imported state:
terraform state list
# Output: aws_security_group.app_sg, aws_instance.app_server
terraform state show aws_security_group.app_sg
# Shows all attributes from AWS
Now run terraform plan again:
No changes. Your infrastructure matches the configuration.
The Configuration Drift Dilemma
Common Pitfall: Imported attributes don't match your Terraform code. Example: The security group description differs.
Your code says: "Security group for application servers" AWS has: "SG for legacy app"
Result: terraform plan shows:
~ description = "SG for legacy app" -> "Security group for application servers"
Solutions:
- Update Terraform configuration to match AWS exactly:
resource "aws_security_group" "app_sg" {
name = "app-security-group"
description = "SG for legacy app" # Match AWS exactly
# ... rest of configuration
}
- Use lifecycle rules to ignore differences:
resource "aws_security_group" "app_sg" {
# ... configuration ...
lifecycle {
ignore_changes = [
description,
tags["CreatedBy"],
tags["CreationDate"]
]
}
}
- Update AWS resource using Terraform:
terraform apply # This updates AWS to match your code
Recovery Scenario: Lost State File
Interview Question: "You deleted your Terraform state file. How do you recover?"
Answer:
Import everything: Use
terraform importfor each resourceBackup restoration: If using S3 backend with versioning, restore previous state
Partial recovery: Import critical resources first, then verify with
terraform plan
# Script to import common resources
terraform import module.vpc.aws_vpc.main vpc-12345678
terraform import module.vpc.aws_subnet.public[0] subnet-87654321
terraform import aws_security_group.app_sg sg-0c398cb65a93047f2
Choosing Your Import Method
Native
terraform import(Precise but manual):Best for few resources
Full control over configuration
Requires resource-by-resource commands
Terraformer (Bulk import):
# Import entire environment terraformer import aws --resources="*" --connect=true # Generates both code and stateAWS2TF (Code generation focus):
# Generate Terraform code from existing resources aws2tf -target sg-12345678 -o generated/
The Complete Workflow
Inventory: List all manual AWS resources (IDs, attributes)
Code: Write Terraform configuration matching existing resources
Import: Execute
terraform importfor each resourceVerify: Check
terraform state listandterraform planCommit: Version control your Terraform code
Manage: Future changes through Terraform only
Video
Final Thoughts
Terraform import transforms manual infrastructure into managed IaC assets. While the process requires careful planning, the benefits are substantial: consistency, auditability, and automated management. Remember that import doesn't modify AWS resources—it just adds them to Terraform's awareness. From that point forward, you have a single source of truth for infrastructure management, bridging the gap between legacy systems and modern DevOps practices.



