🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Terraform Import: Fixing State Conflicts with Existing Resources

Getting state conflicts or duplicate resource errors with terraform import? Learn how to import existing AWS resources, fix config mismatches, and use moved blocks to rename state entries.

DevOpsBoys4 min read
Share:Tweet

Running terraform import is one of those operations that sounds simple until it blows up. You created a resource manually in the AWS console, lost a state file, or onboarded an existing environment — now Terraform doesn't know about it, and you need to bring it under management without destroying and recreating it.

Here's how to do it without breaking your infrastructure.

When You Actually Need terraform import

Three common scenarios:

  1. Resource created manually — someone clicked through the AWS console and now the pipeline fails with a resource already existing
  2. Lost state file — the .tfstate was deleted, corrupted, or was never stored in a remote backend
  3. Onboarding an existing environment — you're writing Terraform for infrastructure that's been running for years

Write the Config First — This is Mandatory

Before you run terraform import, you need a resource block in your .tf files that matches the resource. Import only adds the resource to state; it does NOT write your config for you.

Example: you have an S3 bucket my-app-prod-logs that was created manually.

hcl
resource "aws_s3_bucket" "prod_logs" {
  bucket = "my-app-prod-logs"
}

Now import it:

bash
terraform import aws_s3_bucket.prod_logs my-app-prod-logs

Output:

aws_s3_bucket.prod_logs: Importing from ID "my-app-prod-logs"...
aws_s3_bucket.prod_logs: Import prepared!
  Prepared aws_s3_bucket for import
aws_s3_bucket.prod_logs: Refreshing state... [id=my-app-prod-logs]

Import successful!

The Most Common Problem: Config Doesn't Match

After import, run terraform plan. If the plan shows changes, your config doesn't match the real resource. This is the most frequent issue — and it will cause drift.

bash
terraform plan
 
# Plan shows:
# ~ resource "aws_s3_bucket" "prod_logs" {
#     + tags = {}
#     ~ versioning {
#         + enabled = false  -> true
#       }
# }

Fix: inspect the actual resource with terraform state show, then update your config to match.

bash
terraform state show aws_s3_bucket.prod_logs

This dumps every attribute Terraform read from AWS. Copy the values you care about into your resource block. For attributes you don't want to manage, use lifecycle { ignore_changes = [...] }:

hcl
resource "aws_s3_bucket" "prod_logs" {
  bucket = "my-app-prod-logs"
 
  lifecycle {
    ignore_changes = [tags, cors_rule]
  }
}

After updating config, terraform plan should show no changes.

Error: Resource Already Managed

Error: Resource already managed by Terraform

  on main.tf line 5, in resource "aws_s3_bucket" "prod_logs":
   5: resource "aws_s3_bucket" "prod_logs" {

To import to this address you must first remove the existing object
from the state.

This means the resource address is already in your state file (pointing to a different real resource, or a stale entry). Fix:

bash
# Remove the stale entry from state
terraform state rm aws_s3_bucket.prod_logs
 
# Then re-import
terraform import aws_s3_bucket.prod_logs my-app-prod-logs

Importing Complex Resources: IAM Roles

IAM roles are tricky because Terraform splits the role and its policy attachments into separate resources.

bash
# Import the role itself
terraform import aws_iam_role.app_role my-app-role
 
# Import the policy attachment (format: role-name/policy-arn)
terraform import aws_iam_role_policy_attachment.app_role_policy \
  my-app-role/arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
 
# Import an inline policy (format: role-name:policy-name)
terraform import aws_iam_role_policy.inline_policy \
  my-app-role:my-inline-policy-name

Each of these needs a corresponding resource block before you import. After importing all three, run terraform plan and fix any mismatches.

Using the moved Block for State Renames

If you refactor your Terraform code — moving a resource into a module or renaming it — instead of removing from state and re-importing, use the moved block:

hcl
moved {
  from = aws_s3_bucket.prod_logs
  to   = module.storage.aws_s3_bucket.logs
}

This tells Terraform "the resource at this old address is now at this new address." No destroy, no import needed. After applying, remove the moved block from your config.

Automating Imports with import Block (Terraform 1.5+)

Terraform 1.5 added declarative imports — no CLI command needed:

hcl
import {
  to = aws_s3_bucket.prod_logs
  id = "my-app-prod-logs"
}
 
resource "aws_s3_bucket" "prod_logs" {
  bucket = "my-app-prod-logs"
}

Run terraform plan — it shows the import as part of the plan. Run terraform apply — it imports and applies. This is now the preferred approach for new workflows.

Checklist Before You Import

  • Write the resource block first
  • Use terraform state list to check if address already exists
  • Run terraform plan after import and fix all diffs
  • Store state in a remote backend (S3 + DynamoDB) before doing any of this on a team
  • Never import into a workspace someone else is actively using — lock the state first

The terraform import workflow takes patience, but it's far safer than deleting and recreating resources that have live traffic or data attached to them.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments