TerraformPilot

Cloud Computing

terraform import: Bring Existing Resources Under Terraform Management

Step-by-step guide to terraform import. Import existing AWS, Azure, and GCP resources into Terraform state. Includes import blocks (Terraform 1.5+)

LLuca Berton2 min read

Introduction

#

You have cloud resources running in production that weren't created with Terraform. Maybe they were created manually in the AWS console, or through a different tool. Now you want to manage them with Terraform without recreating them.

That's exactly what terraform import does — it brings existing infrastructure under Terraform management by adding it to the state file.

Quick Start

#

Import an existing AWS EC2 instance:

# 1. Write the resource block in your .tf file
# main.tf
resource "aws_instance" "web" {
  # Configuration will be filled after import
}
 
# 2. Import the resource
terraform import aws_instance.web i-1234567890abcdef0
 
# 3. Verify the import
terraform state show aws_instance.web
 
# 4. Fill in the configuration to match the actual resource
# Run terraform plan — it should show "No changes"

Two Methods: CLI Import vs Import Blocks

#

Method 1: terraform import Command (All Versions)

#

The classic approach — one resource at a time:

terraform import <resource_type>.<name> <resource_id>

Examples:

# AWS EC2 instance
terraform import aws_instance.web i-1234567890abcdef0
 
# AWS S3 bucket
terraform import aws_s3_bucket.data my-bucket-name
 
# AWS Security Group
terraform import aws_security_group.web sg-0123456789abcdef0
 
# AWS VPC
terraform import aws_vpc.main vpc-0123456789abcdef0
 
# Azure Resource Group
terraform import azurerm_resource_group.main /subscriptions/SUB_ID/resourceGroups/my-rg
 
# GCP Compute Instance
terraform import google_compute_instance.web projects/my-project/zones/us-central1-a/instances/my-vm

Method 2: Import Blocks (Terraform 1.5+)

#

Import blocks are declarative — add them to your .tf files and run terraform plan:

# imports.tf
import {
  to = aws_instance.web
  id = "i-1234567890abcdef0"
}
 
import {
  to = aws_s3_bucket.data
  id = "my-bucket-name"
}
 
import {
  to = aws_security_group.web
  id = "sg-0123456789abcdef0"
}

Then run:

terraform plan -generate-config-out=generated.tf

Terraform generates the configuration automatically. Review generated.tf, clean it up, and merge it into your main configuration.

Advantages of import blocks:

  • Can import multiple resources at once
  • Configuration can be auto-generated
  • Import is part of the plan/apply workflow
  • Works with Terraform Cloud

Step-by-Step: Import an AWS EC2 Instance

#

Step 1: Find the Resource ID

#
aws ec2 describe-instances --filters "Name=tag:Name,Values=my-server" \
  --query "Reservations[].Instances[].InstanceId" --output text

Output: i-0abc123def456789

Step 2: Write the Resource Block

#

Create a minimal resource block in your .tf file:

resource "aws_instance" "web" {
  # Required attributes — we'll fill these after import
  ami           = "ami-placeholder"
  instance_type = "t3.micro"
}

Step 3: Run the Import

#
terraform import aws_instance.web i-0abc123def456789

Output:

aws_instance.web: Importing from ID "i-0abc123def456789"...
aws_instance.web: Import prepared!
  Prepared aws_instance for import
aws_instance.web: Refreshing state... [id=i-0abc123def456789]
 
Import successful!

Step 4: Get the Full Configuration

#
terraform state show aws_instance.web

This prints all attributes. Copy the relevant ones into your resource block:

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  subnet_id     = "subnet-0123456789abcdef0"
  key_name      = "my-key"
 
  vpc_security_group_ids = ["sg-0123456789abcdef0"]
 
  root_block_device {
    volume_size = 20
    volume_type = "gp3"
  }
 
  tags = {
    Name = "my-server"
  }
}

Step 5: Verify — No Changes

#
terraform plan

Should show:

No changes. Your infrastructure matches the configuration.

If it shows changes, update your configuration to match the actual resource state.

Bulk Import: Multiple Resources

# #
# imports.tf — import your entire VPC setup
import {
  to = aws_vpc.main
  id = "vpc-0123456789"
}
 
import {
  to = aws_subnet.public_a
  id = "subnet-aaa111"
}
 
import {
  to = aws_subnet.public_b
  id = "subnet-bbb222"
}
 
import {
  to = aws_internet_gateway.main
  id = "igw-0123456789"
}
 
import {
  to = aws_route_table.public
  id = "rtb-0123456789"
}
terraform plan -generate-config-out=generated_vpc.tf

Using a Shell Script

#

For many resources of the same type:

#!/bin/bash
# Import all EC2 instances with a specific tag
INSTANCES=$(aws ec2 describe-instances \
  --filters "Name=tag:Environment,Values=production" \
  --query "Reservations[].Instances[].InstanceId" \
  --output text)
 
i=0
for id in $INSTANCES; do
  terraform import "aws_instance.prod[$i]" "$id"
  ((i++))
done

Import for Different Cloud Providers

#

AWS Common Resources

#
# VPC
terraform import aws_vpc.main vpc-12345
 
# Subnet
terraform import aws_subnet.public subnet-12345
 
# Security Group
terraform import aws_security_group.web sg-12345
 
# RDS Instance
terraform import aws_db_instance.main my-database
 
# IAM Role
terraform import aws_iam_role.lambda my-lambda-role
 
# S3 Bucket
terraform import aws_s3_bucket.logs my-log-bucket
 
# Route53 Zone
terraform import aws_route53_zone.main Z1234567890

Azure Common Resources

#
# Resource Group
terraform import azurerm_resource_group.main \
  /subscriptions/SUB_ID/resourceGroups/my-rg
 
# Virtual Network
terraform import azurerm_virtual_network.main \
  /subscriptions/SUB_ID/resourceGroups/my-rg/providers/Microsoft.Network/virtualNetworks/my-vnet
 
# Storage Account
terraform import azurerm_storage_account.main \
  /subscriptions/SUB_ID/resourceGroups/my-rg/providers/Microsoft.Storage/storageAccounts/mystorageaccount

GCP Common Resources

#
# Compute Instance
terraform import google_compute_instance.web \
  projects/my-project/zones/us-central1-a/instances/my-vm
 
# GKE Cluster
terraform import google_container_cluster.primary \
  projects/my-project/locations/us-central1/clusters/my-cluster
 
# Cloud Storage Bucket
terraform import google_storage_bucket.data my-bucket

Common Errors and Fixes

#

"Resource already managed by Terraform"

#
Error: Resource already managed by Terraform

The resource is already in your state. Check with:

terraform state list | grep aws_instance

"Cannot import non-existent remote object"

#
Error: Cannot import non-existent remote object

The resource ID is wrong or the resource was deleted. Verify it exists:

aws ec2 describe-instances --instance-ids i-1234567890abcdef0

Configuration drift after import

#

If terraform plan shows changes after import, your .tf configuration doesn't match the real resource. Update your config to match, or accept the drift by running apply.

Best Practices

#
  1. Import to a separate state first: Test imports in a workspace before importing into production state
  2. Use import blocks (Terraform 1.5+): They're declarative, reviewable, and support auto-generated configs
  3. Run plan immediately after import: Verify no unexpected changes
  4. Back up your state: cp terraform.tfstate terraform.tfstate.backup before importing
  5. Don't import and modify in the same apply: Import first, verify, then make changes
  6. Tag resources before importing: Makes identification easier
  7. Remove import blocks after apply: They're only needed for the initial import

Hands-On Courses

#

Learn by doing with interactive courses on CopyPasteLearn:

Conclusion

#

terraform import bridges the gap between manually created infrastructure and Terraform management. Use the CLI command for quick one-off imports, and import blocks (Terraform 1.5+) for bulk imports with auto-generated configurations. Always verify with terraform plan after importing to ensure your configuration matches the real infrastructure.

#Terraform#HashiCorp#DevOps#Infrastructure as Code#Cloud Management#terraform import

Share this article