Terraform Import AWS: Adopt Existing Resources Into Terraform State Without Recreation

Terraform import is the process of bringing existing cloud resources created manually, by another tool, or before you adopted Terraform under Terraform management by recording them in your state file. There are two ways to do it: the legacy terraform import command and the modern import {} block introduced in Terraform 1.5.

This guide covers both approaches for AWS, when to use each, complete examples for EC2 instances, S3 buckets, RDS databases, VPCs, security groups, and IAM roles, how to generate Terraform code from existing resources, how to import multiple resources at once, and the common errors that trip people up. Whether you are importing a single S3 bucket or migrating an entire estate of brownfield infrastructure, a working pattern is below.

How Terraform Import Works for AWS

Terraform import takes an existing resource and records it in your Terraform state file. After import, Terraform manages that resource the same as if it had been created by terraform apply from the start. You can then update it, plan changes against it, and destroy it through code.

The import command finds the existing resource from ID and imports it into Terraform state at the given ADDRESS. ADDRESS must be a valid resource address. Because any resource address is valid, the import command can import resources into modules as well as directly into the root of your state.

ID is dependent on the resource type being imported. For example, for AWS EC2 instances it is the instance ID i-abcd1234 but for AWS Route53 zones it is the zone ID Z12ABC4UGMOZ2N. Please reference the provider documentation for details on the ID format.

Two important caveats up front:

  • The legacy terraform import command only writes state. The modern import {} block can be paired with terraform plan -generate-config-out=generated.tf to generate draft HCL. Treat generated code as a starting point. It is faithful to the live resource but does not match your conventions, may include computed attributes you do not want managed, and rarely needs zero cleanup.
  • You still have to write the matching resource block by hand, or use code generation. There is no automatic resource block creation.

The Two Import Methods

Legacy CLI Command

The terraform import CLI command is available since Terraform 0.7. It is imperative, one-off, writes to state directly without a plan preview.

Usage:
terraform import [options] ADDRESS ID

Import will find the existing resource from ID and import it into your Terraform state at the given ADDRESS.

Write a resource block for the resource you want to import in your configuration. Provide a name for the resource, which is a unique ID that you can use to reference the resource elsewhere in the configuration.

Example for an AWS instance:
resource "aws_instance" "example" { # ...instance configuration... }
You do not have to complete the body of the resource block. Instead, you can finish defining arguments after the instance is imported.

Run terraform import to attach an existing instance to the resource configuration:
$ terraform import aws_instance.example i-abcd1234
This command locates the AWS EC2 instance with ID i-abcd1234. Then it attaches the existing settings of the instance, as described by the EC2 API, to the name aws_instance.example of a module. In this example, the module path implies that the root module is used. Finally, the mapping is saved in the Terraform state.

It is also possible to import to resources in child modules, using their paths, and to single instances of a resource with count or for_each set.

Modern Import Block

The import {} block was introduced in Terraform 1.5.0.

import { to = aws_instance.app_server id = "i-1234567890abcdef0" }

Once terraform apply completes successfully and the resource is in state, the import {} block is no longer doing anything. You can remove it from your configuration. The resource block stays.

The modern block can be paired with terraform plan -generate-config-out=generated.tf to generate draft HCL.

Prerequisites for AWS Import

Most organizations do not start with Terraform from day one. They have existing AWS resources created through the console, CLI, or CloudFormation that need to be brought under Terraform management. The terraform import command and the import block let you adopt existing resources into your Terraform state without recreating them.

A minimal configuration:
terraform { required_version = ">= 1.5.0" required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "us-east-1" }

Importing EC2 Instances

First, write the resource configuration:
```

ec2.tf - Configuration for existing EC2 instance

resource "awsinstance" "appserver" {
ami = "ami-0abcdef1234567890"
instancetype = "t3.large"
tags = {
Name = "app-server-production"
}
lifecycle {
ignore
changes = [ami]
}
}
```

Import using the command:
terraform import aws_instance.app_server i-1234567890abcdef0

Or use the import block:
import { to = aws_instance.app_server id = "i-1234567890abcdef0" }

After import, run terraform plan to verify the import works and your HCL matches.

Importing S3 Buckets and Related Resources

For AWS S3 specifically, provider v4.0+ split many bucket settings into separate resources awss3bucketversioning, awss3bucketacl, etc., so you may need to import related resources separately depending on what you want Terraform to manage.

resource "aws_s3_bucket" "data" { bucket = "my-company-data-bucket" }

Import:
import { to = aws_s3_bucket.data id = "my-company-data-bucket" }

Versioning is a separate resource:
resource "aws_s3_bucket_versioning" "data" { bucket = aws_s3_bucket.data.id versioning_configuration { status = "Enabled" } }
import { to = aws_s3_bucket_versioning.data id = "my-company-data-bucket" }

Importing VPCs and Networking

resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" tags = { Name = "main-vpc" } }

resource "aws_subnet" "public" { vpc_id = aws_vpc.main.id cidr_block = "10.0.1.0/24" tags = { Name = "public-subnet-1" } }

resource "aws_security_group" "web" { name = "web-sg" vpc_id =
The pattern is to define the resource block with approximate arguments, then import using the AWS ID for the resource.

Importing IAM Roles and Multiple Resources

Suppose you want to manage multiple IAM roles with Terraform that already exist. You can define resources with for_each and import each individually, or repeat import blocks for each role.

Common AWS resources that can be imported include EC2 instances, S3 buckets, RDS databases, VPCs, security groups, and IAM roles.

Referring to the plan output, identify the resources in AWS and repeat the process for import.

Common Errors and Safeguards

Terraform expects that each remote object it is managing will be bound to only one resource address, which is normally guaranteed by Terraform itself having created all objects. If you import existing objects into Terraform, be careful to import each remote object to only one Terraform resource address. If you import the same object multiple times, Terraform may exhibit unwanted behavior.

For more information on this assumption, see the State section.

If you import the same object multiple times, Terraform may exhibit unwanted behavior.

Terraform will refuse and return an error: Resource already managed by Terraform. If you need to move a resource to a new address, use terraform state mv instead of importing. If you need to re-import a resource, first remove it from state with terraform state rm, then import to the new address.

The syntax of the given ID is dependent on the resource type being imported.

Warning: Terraform expects that each remote object it is managing will be bound to only one resource address, which is normally guaranteed by Terraform itself having created all objects. If you import existing objects into Terraform, be careful to import each remote object to only one Terraform resource address. If you import the same object multiple times, Terraform may exhibit unwanted behavior.

If a resource does not support import, you will see an error such as This resource does not support import. Please contact the provider developer for additional information. In that case you may need to read attributes from the existing object and recreate it with matching settings.

Import Method Comparison

Aspect terraform import command import {} block
Introduced Terraform 0.7 Terraform 1.5.0
Style Imperative, one-off Declarative, configuration
State write Writes directly without plan preview Paired with plan
Code generation No Can use terraform plan -generate-config-out
Removal after apply N/A Can be removed after resource is in state

Workflow Checklist

  • Write a resource block for the AWS resource you want to manage
  • Identify the correct AWS ID for the resource type
  • Run terraform import
    or add import {} block
  • Run terraform plan to verify HCL matches live resource
  • Adjust the resource block to remove computed attributes you do not want managed
  • Run terraform apply to finalize adoption

Conclusion

Adopting existing AWS infrastructure into Terraform with import is a practical way to move from brownfield to code without recreation. The legacy terraform import command remains the fastest way to attach a single resource to state, while the import {} block introduced in Terraform 1.5 brings import into configuration and enables draft HCL generation via terraform plan -generate-config-out.

For AWS, success depends on using the correct ID format per resource type, writing an initial resource block that matches the live object closely enough for plan to succeed, and respecting the one-object-one-address rule to avoid unwanted behavior. Provider v4+ splits for S3 mean you often need to import bucket and its related settings as separate resources.

Once terraform apply completes successfully, the import block is no longer doing anything and can be removed. The resource block stays and Terraform will manage the resource the same as if it had been created by terraform apply from the start.

Sources

  1. Scalr Learning Center
  2. HashiCorp Terraform CLI
  3. OneUptime Blog
  4. HashiCorp Terraform Import Usage
  5. Spacelift Blog

Related Posts