Terraform

Mastering Terraform import: A Safe Guide to Onboarding Existing Resources

2026-04-19
NicheeLab Editorial Team

terraform import is the first step when bringing existing cloud assets under Terraform management. It is a "copy into state" operation and does not auto-generate configuration files. The crux, therefore, is how you keep state and configuration in sync.

This article compares the import block recommended since Terraform 1.5 with the legacy terraform import CLI that is still available, focusing on safety and exam preparation.

Core Concepts and Prerequisites for Importing

terraform import links an existing resource to Terraform's state (tfstate). From that point on, the resource is subject to terraform plan/apply. However, import itself does not generate configuration (HCL); you must author the configuration by hand.

Importing is all about "the correct address with the correct ID." The ID format varies by resource type, so confirm the format in the provider's official documentation. It is also assumed that credentials and provider configuration are correctly set up beforehand.

  • import does not destroy existing entities; it only maps them into tfstate
  • Configuration is not generated automatically; you must write resource blocks to reconcile state
  • ID format differs per resource (e.g. aws_s3_bucket uses the bucket name, aws_iam_role uses the role name)
  • Configure workspace, backend, and state locking appropriately before running import

Minimal resource definition example (target of import)

resource "aws_s3_bucket" "example" {
  bucket = "my-existing-bucket"
  # Start with the minimum attributes that match the real environment
}

Understanding tfstate and Resource Addresses

For import, "where you bind it" matters. A Terraform resource address is composed of module hierarchy, type, name, and index (count/for_each). Importing to the wrong address forces later renames or re-imports, increasing risk and rework.

data sources cannot be imported. Only managed resources can be imported. To import into a module, use the full address including the module.<name> hierarchy.

  • Top level: aws_instance.web
  • With count: aws_instance.web[0]
  • With for_each: aws_instance.web["blue"]
  • Inside a module: module.app.aws_instance.web
  • data sources are not allowed; only resources are eligible

Representative address examples

# Single
# terraform import aws_instance.web i-0123456789abcdef0

# Index 0 of count
# terraform import aws_instance.web[0] i-0aaa...

# for_each with key=blue
# terraform import aws_instance.web["blue"] i-0bbb...

# Inside a module
# terraform import module.app.aws_instance.web i-0ccc...

Production Workflow: import block (Terraform 1.5+)

From Terraform 1.5 onward, the recommended workflow is to declare an import block in configuration and then import via plan/apply. The advantages are that it can be reviewed as code and is highly reproducible. The import block takes to and id: to is the destination resource address, and id is the existing resource's ID.

Both the import block and a matching resource block must exist in configuration. Review the diff with plan and import into state with apply. Then, set attributes explicitly as needed to reduce drift.

  • Managed as code, so it fits naturally into PR review
  • import appears in the plan output, enabling a safe decision before applying
  • You can delete the import block after the import (or leave it as historical record)

Flow of the import block approach

readmapdeclarereconcileExisting CloudResource (ID)Provider SDKRead by IDTerraform StateAddress: toimport blockresource blockterraform planFlow of the import block approach

Example of an import block with a matching resource (S3 bucket)

resource "aws_s3_bucket" "example" {
  bucket = "my-existing-bucket"
}

import {
  to = aws_s3_bucket.example
  id = "my-existing-bucket"
}

# Run
# terraform init
# terraform plan   # Review the proposed import
# terraform apply  # Import into state

Legacy CLI Approach: the terraform import Command

The legacy terraform import ADDRESS ID is still available. It writes to state immediately. If no configuration exists (or attributes don't match) after importing, the next plan will show large diffs, so you must shape the configuration before and after the import.

Be careful with addresses for modules and for_each/count. If you import to the wrong address, terraform state mv can move it, but it is safer to plan ahead.

  • A single command reflects to state, so it's fast but hard to review
  • If configuration is not in place, plan will show a large diff
  • Use terraform state rm / mv to adjust mistaken imports
Aspectimport block (1.5+)terraform import CLIExam Note
Reproducibility / AuditabilityHigh because it lives in codeAd-hoc commands; history is hard to keepUnderstand that a code-managed approach is the better choice
Timing of ExecutionPerformed as part of plan/applyState updates immediatelyDifference lies in whether it appears in plan
ReviewabilityReviewable in PRsTends to be verbal or runbook-basedTeam-operation perspective is commonly tested
Config GenerationNot auto-generatedNot auto-generated"import does not create configuration" is a classic exam point
RollbackSafely applied via plan diffReversing mistakes is laboriousAlso know the use cases for state mv/rm

CLI examples and recovery operations

# Import (identifying by IAM role name)
terraform import aws_iam_role.app_role app-role

# Inside a module
terraform import module.app.aws_s3_bucket.logs my-log-bucket

# Move a mistaken import
terraform state mv aws_s3_bucket.wrong module.app.aws_s3_bucket.logs

# Delete a mistaken import (the actual resource is not deleted)
terraform state rm aws_s3_bucket.wrong

Common Pitfalls and Best Practices

If plan shows a large diff right after import, the configuration isn't expressing the real entity well enough. The more you set attributes explicitly to match the live environment, rather than relying on strong defaults or computed values, the less drift you'll see. In particular, write encryption, lifecycle, tags, and deletion protection explicitly.

Decide on count/for_each and module hierarchy before importing to reduce later moves. When you need to rename, the moved block can safely transfer addresses. For large migrations, do not go all-at-once; proceed incrementally starting with low-importance resources, and rigorously back up state.

  • Back up tfstate before importing (even with a remote backend, just in case)
  • Don't pick the wrong workspace (never import production into default)
  • Read plan diffs carefully; consider refresh-only as well
  • Lock down naming and module design first
  • Use moved blocks to rename safely

Examples of for_each imports and moved blocks

# for_each example (manage keys per tag value)
resource "aws_kms_key" "keys" {
  for_each = toset(["blue", "green"])
  description = "key-${each.key}"
}

import {
  to = aws_kms_key.keys["blue"]
  id = "1234abcd-...-blue-key-id"
}
import {
  to = aws_kms_key.keys["green"]
  id = "5678efgh-...-green-key-id"
}

# Safe address migration
moved {
  from = aws_kms_key.keys["blue"]
  to   = module.security.aws_kms_key.keys["blue"]
}

Exam Prep Points and an Ops Command Cheat Sheet

On the exam, the recurring points are: "import only loads into state and does not create configuration," "specify the correct resource address and ID," "data sources are excluded," "handling of module/for_each/count," and "differences between import block and the legacy CLI." Seeing the import proposal in plan before apply is the hallmark of the import block approach.

In operations, the key is to verify state with terraform state show after import and close the gap with configuration. Master state mv/rm for mistaken imports, the moved block for renames, state backup and locking, and proper workspace usage.

  • import is non-destructive, but mistakes are costly to recover from
  • After import, run state show, add config, then plan to shrink the diff
  • Always confirm workspace and backend consistency

Frequently used inspection and adjustment commands

# Inspect state
terraform state list
terraform state show aws_s3_bucket.example

# Safe state moves
terraform state mv aws_s3_bucket.old aws_s3_bucket.new

# Switch workspace
terraform workspace select staging

Check Your Understanding

Associate / Pro

問題 1

A team uses Terraform 1.5+ and wants to safely bring an existing S3 bucket under Terraform management. They want a reviewable, reproducible procedure. Which approach is most appropriate?

  1. Add a resource block and an import block to configuration, review the diff with terraform plan, then import via terraform apply
  2. Import to state with the terraform import command first, then write configuration to reduce the plan diff
  3. Delete the resource once and recreate it with terraform apply to align state and configuration
  4. Define it as a data source and import into state with terraform refresh

正解: A

In 1.5+, managing the import block as code and importing via plan/apply is the recommended flow. It is highly reviewable and reproducible. The terraform import CLI also works, but state changes immediately, making review harder. Delete-and-recreate is unnecessary and risky. data sources cannot be imported.

Frequently Asked Questions

Does import auto-generate configuration files?

No. import only links an existing resource to tfstate; it does not generate HCL. After importing, run terraform state show, then add the necessary attributes to your configuration to reduce drift.

How do I import resources that use for_each or count?

Import each element with its own address. Use [index] for count and ["key"] for for_each. Declare multiple import blocks, or run terraform import ADDRESS ID once per element via the CLI.

I accidentally imported to the wrong address. Can I undo it?

Without deleting the resource, you can safely move it to the correct address with terraform state mv. Unwanted entries can be removed from state only with terraform state rm. Neither command deletes the actual cloud resource.

Check what you learned with practice questions

Practice with certification-focused question sets

無料で問題を解いてみる
Author

NicheeLab Editorial Team

NicheeLab editorial team focused on data engineering and cloud certification learning. Content is structured around practical study needs and official exam domains.


Related articles
Terraform

HCL Syntax: Terraform's Configuration Language (2026)

HCL2 fundamentals for Terraform — blocks, attributes, expres...

Terraform

Terraform Authoring & Operations Pro: Complete Guide (2026)

Tactics for the Terraform Pro exam — module authoring, works...

Terraform

Terraform Providers: Plugin Management Fundamentals (2026)

Provider mechanics — required_providers, versions, mirrors, ...

Terraform

Terraform Resource Blocks: Declarative Infra Units (2026)

Resource block fundamentals — addresses, references, common ...

Terraform

Terraform Data Sources: Read-Only External Data (2026)

Data source basics — declaration, refresh behavior, dependen...

Browse all Terraform articles (102)
© 2026 NicheeLab All rights reserved.