Skip to main content

Command Palette

Search for a command to run...

Day 25: Terraform Import In AWS

Updated
4 min read
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:

  1. Reads desired state from .tf files

  2. Compares with actual state from tfstate

  3. Calculates differences

  4. 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-group with SSH(22), HTTP(80), HTTPS(443) access

  • EC2 Instance: t2.micro with this security group attached

  • Note IDs: sg-0c398cb65a93047f2 (SG) and i-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:

  1. 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
}
  1. Use lifecycle rules to ignore differences:
resource "aws_security_group" "app_sg" {
  # ... configuration ...

  lifecycle {
    ignore_changes = [
      description,
      tags["CreatedBy"],
      tags["CreationDate"]
    ]
  }
}
  1. 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:

  1. Import everything: Use terraform import for each resource

  2. Backup restoration: If using S3 backend with versioning, restore previous state

  3. 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

  1. Native terraform import (Precise but manual):

    • Best for few resources

    • Full control over configuration

    • Requires resource-by-resource commands

  2. Terraformer (Bulk import):

     # Import entire environment
     terraformer import aws --resources="*" --connect=true
     # Generates both code and state
    
  3. AWS2TF (Code generation focus):

     # Generate Terraform code from existing resources
     aws2tf -target sg-12345678 -o generated/
    

The Complete Workflow

  1. Inventory: List all manual AWS resources (IDs, attributes)

  2. Code: Write Terraform configuration matching existing resources

  3. Import: Execute terraform import for each resource

  4. Verify: Check terraform state list and terraform plan

  5. Commit: Version control your Terraform code

  6. 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.