How to Use Terraform Import Block for Importing Resources
New to Terraform import blocks? This guide walks you through your first import step-by-step, bringing existing cloud resources under management.
So you have infrastructure running in the cloud that wasn't created with Terraform. Maybe it was set up manually through the AWS console, or by another team. How do you bring those existing resources under Terraform's management without having to delete and recreate them?
The answer is the import block. Introduced in Terraform v1.5, it's a safe and predictable way to tell Terraform: "Hey, see this existing resource? I want you to manage it from now on."
This guide will walk you through the process step-by-step, from your first simple import to handling more complex scenarios.
Why Import Blocks Matter
Before import blocks, bringing existing resources into Terraform meant using the terraform import
CLI command - a risky operation that modified your state immediately with no preview. Import blocks revolutionized this process by integrating imports into the standard plan/apply workflow.
Import Block vs. The Old terraform import Command
Feature/Aspect | import Block (Modern Way) | terraform import CLI (Old Way) |
---|---|---|
Preview | Yes, you can see the plan before any changes | No, it modifies your state file immediately |
HCL Generation | Yes, can auto-generate a draft for you | No, you must write all HCL manually |
Safety | High. It's part of the plan/apply workflow | Low. It's easy to make mistakes |
CI/CD | Works perfectly in automation pipelines | Risky and difficult to use in CI/CD |
Version Control | Import definitions can be reviewed in PRs | No visibility into what's being imported |
Your First Import: Step-by-Step S3 Bucket Example
Let's walk through importing an S3 bucket named my-legacy-data-bucket
that was created manually.
Step 1: Find Your Resource's ID
First, Terraform needs to know how to find the exact resource in your cloud account. For an S3 bucket, the unique ID is simply its name. For other resources, like an EC2 instance, it would be the instance ID (e.g., i-012345abcdef
).
Our Resource ID: my-legacy-data-bucket
Step 2: Create the Import and Resource Blocks
In your Terraform configuration, you need two things:
- An empty resource block (in your main configuration)
- An import block (we suggest a new file like
imports.tf
to keep things organized)
# In your main.tf file
resource "aws_s3_bucket" "legacy_bucket" {
# We'll fill this in soon. For now, it can be empty.
}
# In a new file: imports.tf
import {
to = aws_s3_bucket.legacy_bucket # This is the address inside Terraform
id = "my-legacy-data-bucket" # This is the real bucket name in AWS
}
The to
address tells Terraform which resource block in your code will manage this imported resource.
Step 3: Generate the Configuration
This is where the magic happens. Instead of manually writing out the HCL for your bucket's configuration, Terraform can create a first draft for you.
Run the following command:
terraform plan -generate-config-out=generated.tf
Terraform will inspect the real S3 bucket in AWS and write a new file, generated.tf
, containing its best guess for the HCL configuration.
Step 4: The Most Important Step - Review and Refine
Do not blindly trust the generated.tf file. Think of it as a helpful starting point, not a finished product.
Open generated.tf
. You will likely see many attributes. Your job is to:
- Clean it up: Remove arguments that are computed (like
arn
,hosted_zone_id
) or attributes set to their default values that you don't need to specify. - Correct it: Sometimes Terraform gets things wrong or generates invalid syntax. Fix any errors.
- Make it yours: Refactor the code to fit your team's standards. Use variables where appropriate.
Here's an example of what you might see and how to clean it:
# BEFORE - Generated configuration
resource "aws_s3_bucket" "legacy_bucket" {
bucket = "my-legacy-data-bucket"
bucket_domain_name = "my-legacy-data-bucket.s3.amazonaws.com" # Computed - remove
bucket_regional_domain_name = "my-legacy-data-bucket.s3.us-east-1.amazonaws.com" # Computed - remove
hosted_zone_id = "Z3AQBSTGFYJSTF" # Computed - remove
region = "us-east-1" # Computed - remove
request_payer = "BucketOwner" # Default value - remove unless intentionally set
grant {
id = "1234567890abcdef" # Computed - remove entire block
permissions = ["FULL_CONTROL"]
type = "CanonicalUser"
}
}
# AFTER - Cleaned configuration
resource "aws_s3_bucket" "legacy_bucket" {
bucket = "my-legacy-data-bucket"
tags = {
Name = "Legacy Data Bucket"
Environment = "production"
}
}
Once you are happy with the refined HCL, copy the contents of the resource block into your main.tf
and delete the generated.tf
file.
Step 5: Run a Final Validation Plan
Now that your main.tf
has the refined configuration, run a normal plan:
terraform plan
The output should show "1 to import, 0 to change, 0 to destroy." If Terraform still wants to make changes, it means your HCL configuration doesn't perfectly match the real resource's state. Go back to your HCL and adjust it until the plan is clean.
Step 6: Apply to Complete the Import
Once the plan looks correct, apply it:
terraform apply
Terraform will ask for confirmation. Type yes
, and it will officially pull the S3 bucket into your Terraform state file.
Step 7: Verify and Clean Up
You can double-check that the resource is now in your state by running:
terraform state show aws_s3_bucket.legacy_bucket
Finally, you can now delete the import block from your imports.tf
file. Its one-time job is done, and the resource is now fully managed by Terraform.
Common Mistakes to Avoid
- Blindly Trusting Generated Code: As mentioned in Step 4, this is the #1 mistake. Always review, clean, and correct the HCL from
-generate-config-out
. - Forgetting the Resource Block: The import block only tells Terraform what to import. You must have a corresponding
resource "..." "..."
block to tell Terraform how to manage it going forward. - Leaving Import Blocks in Configuration: Import blocks should be removed after successful import. They're one-time operations, not permanent configuration.
- Wrong Resource Addressing: Missing array indices for
count
resources or keys forfor_each
resources is a common error. - Dynamic Value Attempts: Trying to use data sources or computed values in import blocks won't work - all values must be known at plan time.