Importing existing infrastructure into Terraform state
Bring click-ops resources under Terraform with import blocks and generated config — without recreating them.
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 adds to state only — it does not change the cloud resource. Plan must show no destroy/recreate before you trust the generated config.
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 {to = aws_s3_bucket.app_dataid = "acme-app-data-prod"}import {to = aws_s3_bucket_versioning.app_dataid = "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.
terraform plan -generate-config-out=generated.tf# Review generated.tf — merge into main.tfterraform plan# Must show: no changes, or only expected tag updates
terraform plan -generate-config-out=generated.tfaws_s3_bucket.app_data: preparing import...Generating configuration for aws_s3_bucket.app_dataterraform applyImport complete — 2 resources importedterraform planNo changes. Your infrastructure matches the configuration.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.