Terraform Import
The terraform import command brings an existing cloud resource that was created outside of Terraform under Terraform management by adding it to the state file. After importing, Terraform can plan and apply changes to that resource without recreating it.
What Is Terraform Import?
You joined a company where the entire AWS account was built by clicking in the console over three years. There are 200 EC2 instances, 50 S3 buckets, and a VPC with 30 subnets — none of it managed by Terraform. The team wants to adopt Terraform without destroying and recreating everything.
terraform import is how you do it. It reads an existing cloud resource and writes its current state into the Terraform state file. Once imported, Terraform knows the resource exists. You can then write HCL that matches it, and Terraform will manage all future changes through code.
Remember
terraform importadds the resource to state — it does NOT write the HCL configuration for you (except with the newer-generate-config-outflag in Terraform 1.5+). You still need to write the resource block yourself.
The Classic Two-Step Import Process
Step 1 — Write the Resource Configuration
Write a resource block in your .tf files that matches the existing resource. You do not need every argument yet — just enough to make Terraform accept the block:
# main.tf — write this BEFORE running terraform importresource "aws_instance" "payments_api" { # Arguments will be filled in after import # The block just needs to exist for import to work ami = "ami-0f5ee92e2d63afc18" # fill in the real values instance_type = "t3.medium" tags = { Name = "razorpay-payments-api-prod" }}Step 2 — Run terraform import
# Syntax: terraform import <resource_type>.<resource_name> <real_resource_id>terraform import aws_instance.payments_api i-0a1b2c3d4e5f6789 # aws_instance.payments_api: Importing from ID "i-0a1b2c3d4e5f6789"...# aws_instance.payments_api: Import prepared!# Prepared aws_instance for import# aws_instance.payments_api: Refreshing state... [id=i-0a1b2c3d4e5f6789]## Import successful!# The resources that were imported are shown above. These resources are now in# your Terraform state and will henceforth be managed by Terraform.Step 3 — Run terraform plan and Fix Mismatches
After import, run terraform plan. It will show differences between what you wrote in HCL and what is actually on the resource. Fix each difference:
terraform plan # ~ aws_instance.payments_api will be updated in-place# ~ instance_type = "t3.medium" -> "t3.large" <- real instance is t3.large# ~ monitoring = false -> true <- real instance has monitoring on## Fix your HCL to match the real resource, then re-plan until the output is:# No changes. Your infrastructure matches the configuration.Finding the Resource ID for Common AWS Resources
# EC2 Instanceaws ec2 describe-instances --filters "Name=tag:Name,Values=razorpay-payments-api"# ID format: i-0a1b2c3d4e5f6789terraform import aws_instance.payments_api i-0a1b2c3d4e5f6789 # S3 Bucket# ID is just the bucket nameterraform import aws_s3_bucket.assets razorpay-assets-prod # Security Groupaws ec2 describe-security-groups --filters "Name=group-name,Values=payments-api-sg"# ID format: sg-0a1b2c3d4e5f6789terraform import aws_security_group.payments sg-0a1b2c3d4e5f6789 # RDS Instance# ID is the DB identifierterraform import aws_db_instance.main razorpay-payments-postgres-prod # IAM Role# ID is the role nameterraform import aws_iam_role.app_role razorpay-payments-api-role # VPCaws ec2 describe-vpcs --filters "Name=tag:Name,Values=razorpay-prod-vpc"# ID format: vpc-0a1b2c3d4e5fterraform import aws_vpc.main vpc-0a1b2c3d4e5fThe New Import Block (Terraform 1.5+)
Terraform 1.5 introduced a declarative import block that lives in your .tf files — no CLI command needed. Import happens automatically during terraform apply:
# imports.tf — declarative import (Terraform 1.5+)import { to = aws_instance.payments_api id = "i-0a1b2c3d4e5f6789"} import { to = aws_s3_bucket.assets id = "razorpay-assets-prod"}Auto-Generating HCL with -generate-config-out (Terraform 1.5+)
Terraform 1.5 can generate the HCL resource block automatically from an import block:
# Add an import block with no corresponding resource block# Then run:terraform plan -generate-config-out=generated.tf # Terraform reads the real resource and writes HCL into generated.tf# Review and clean up generated.tf — it includes every attribute# including computed ones you do not need to specifyTroubleshooting Import
| Error | Root Cause | Fix |
|---|---|---|
Resource already managed by Terraform |
Resource already in state | Check terraform state list — may already be imported |
Cannot import non-existent remote object |
Wrong resource ID | Verify the ID in the AWS console or CLI |
Error: Resource type does not support import |
Provider does not support import | Check the provider docs — not all resources are importable |
| Plan shows large diff after import | HCL does not match real resource | Run terraform state show to see all attributes, update HCL to match |
InvalidInstanceID.NotFound |
Wrong EC2 instance ID format | Must start with i- followed by hex characters |
Common MistakeRunning
terraform applyimmediately after import without checking the plan. If your HCL does not exactly match the real resource, apply will change the resource to match your code — potentially disabling security settings or changing production configuration.
Frequently Asked Questions
Does terraform import write the HCL configuration for me, or just update the state?
It only adds the resource to the state file — it does not generate the matching `.tf` configuration block. You still have to hand-write (or use `terraform plan -generate-config-out` in newer Terraform versions, which does generate a starting config) the resource block yourself, and if your written config doesn't match the real resource's actual attributes, the very next `terraform plan` will show a diff trying to change it back to match your config.
What's the safest way to import a resource without accidentally modifying it?
Import it, then immediately run `terraform plan` before touching anything else — if the plan shows changes, your HCL doesn't match reality yet, and applying at that point would modify the live resource to match your (likely incomplete) config, not the other way around. Iterate on the config until `terraform plan` shows no changes; only then is the resource safely under Terraform management without unintended drift being 'fixed' by an apply.