BlogIaC

Importing existing infrastructure into Terraform state

Bring click-ops resources under Terraform with import blocks and generated config — without recreating them.

Sep 10, 2024·4 min readIntermediate·By the SecOpsLog team · command-tested

Not every stack starts greenfield. Years of click-ops leave S3 buckets, security groups, and RDS instances that production depends on — but Terraform has never heard of them. Import brings live resources under Terraform management by associating real cloud ids with addresses in state. Terraform 1.5+ import blocks declare the mapping in code; terraform plan -generate-config-out drafts the HCL so you are not hand-writing forty attributes from the console.

This note walks a safe import: read-only discovery, import block, generated config review, plan until zero drift, then apply. The goal is state alignment, not a big-bang rewrite. Brush up on Terraform state mechanics before importing anything customer-facing.

Import without recreation

Import adds to state only — it does not change the cloud resource. Plan must show no destroy/recreate before you trust the generated config.

1Inventory resourceARN, id, current settings2Write import blockto = resource address3terraform plan-generate-config-out=generated.…4Review generatedHCLfix tags, lifecycle5Plan until cleanno unexpected changes6terraform applystate now owns resource7Remove importblockoptional cleanup commit

Declare the import in HCL

The import block maps a Terraform resource address to the provider's import id format. For AWS S3, that is the bucket name; for security groups, sg-xxxxxxxx. Run terraform plan first — it validates the import id before writing state.

Complex resources — RDS instances, EKS clusters, IAM roles with inline policies — often need multiple import blocks for child resources (subnet groups, policy attachments). Import one logical service at a time and plan between each batch so a partial import does not leave state half-owned and half-orphaned in the console.

import.tf
import {
to = aws_s3_bucket.app_data
id = "acme-app-data-prod"
}
import {
to = aws_s3_bucket_versioning.app_data
id = "acme-app-data-prod"
}

Generate config from live attributes

Pass -generate-config-out=generated.tf on plan. Terraform reads the remote object and writes a resource block matching current settings. Move the block into your module, add lifecycle { ignore_changes = [...] } only where intentional drift exists, and delete attributes that should be managed elsewhere. Never apply a generated file blindly — provider defaults and read-only attributes differ from what you would have written by hand.

Use moved blocks when refactoring imported resources into modules without destroy/recreate. Schedule drift detection — nightly terraform plan in CI should report zero changes on imported stacks. When plan shows unexpected replacement, stop and diff the live API response against your HCL before anyone runs apply.

bash
terraform plan -generate-config-out=generated.tf
# Review generated.tf — merge into main.tf
terraform plan
# Must show: no changes, or only expected tag updates
bash — import workflow outputlive
terraform plan -generate-config-out=generated.tf
aws_s3_bucket.app_data: preparing import...
Generating configuration for aws_s3_bucket.app_data
terraform apply
Import complete — 2 resources imported
terraform plan
No changes. Your infrastructure matches the configuration.
Import is not a backup strategy
A bad generated config followed by apply can still change live resources — encryption settings, public access blocks, deletion protection. Always plan twice: once after generate, once after you edit the HCL. Take AWS Config snapshots or manual console exports before the first apply on critical resources.
Import vs replace
Import when
Resource must stay up
Config matches intent
Gradual IaC adoption
Known stable id
Replace when
Resource is misconfigured badly
Name/id must change anyway
Greenfield module exists
Maintenance window allows it

Where this goes next

After import, enforce drift detection in CI — terraform plan on schedule should stay empty. Scan imported code with Checkov to close gaps the legacy resource carried. For large estates, tools like Terraformer or manual module extraction come next. The Terraform course covers import blocks, moved blocks, and state refactoring safely.

Go deeper in a courseTerraformImport, state moves, modules, and production-grade apply workflows.View course

Related posts