Domain 7 of 8 · Chapter 1 of 3

Import existing infrastructure

The classic terraform import CLI command

You created a storage bucket by hand months ago, and now you want Terraform to own it without tearing it down and rebuilding. The original tool for that is terraform import[1], the command that binds one already-existing remote object to a resource address in your configuration by recording it in Terraform state[2]. It changes nothing in the real world: it only teaches Terraform that the object exists and which address manages it from now on. This section covers the command's two positional arguments, why the target resource block has to exist before you run it, and the fact that import writes only to state.

Write the resource block first

The command takes exactly two positional arguments:

terraform import ADDRESS ID

ADDRESS is the resource address[3] in your configuration, such as aws_s3_bucket.assets, and ID is the provider's own identifier for the real object, such as the bucket name. The ID format is defined by the resource type, not chosen by Terraform, so each resource type's documentation states what its import ID looks like.

Because the command binds an object to an address, that resource block must already exist. Importing via the CLI does not generate configuration[4]: point terraform import at an address with no matching resource block and it fails. The normal routine is to write a skeleton resource block, run the import, then run terraform plan and fill in arguments until the plan reports no changes, which is the signal that your configuration now matches the imported object.

It only writes state

terraform import can only import resources into state[4]. It does not create, update, or destroy real infrastructure, and it does not write any HCL for you. It also imports one object per invocation, so adopting ten objects means running the command ten times. There is no provider-specific variant such as terraform import-aws, and terraform refresh[5] does not help either: refresh only reconciles state for resources Terraform already manages and never discovers or adopts an unmanaged object.

Write the target resource block terraform import ADDRESS ID Object recorded in Terraform state terraform plan should show no changes
The classic terraform import CLI flow: the resource block is written first, then import records the object in state, and a clean plan confirms the match.

Config-driven import blocks

Running one CLI command per object does not fit code review or a large migration, so Terraform 1.5 introduced the import block[6]: a way to declare imports in configuration and run them through the ordinary workflow. An import block names where the object goes and what it is.

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

to and id

The to argument[7] is the resource address to import into, and the id argument is the cloud provider's identifier for the existing object. Do not swap them: to is the Terraform address, id is the real object's provider ID. The id must be a string or an expression that is known at plan time[7], so it can reference an input variable but cannot depend on a value that is only known after apply. A single block imports one object; adding for_each[7] to the block imports a whole map or set of similar objects without a separate block for each.

Plan and apply, then delete the block

An import block runs through the normal plan and apply workflow[6]: terraform plan shows the import alongside any other planned changes, and terraform apply carries it out. There is no separate import subcommand. Like the CLI command, the import block only affects state; the real object is untouched. By default the to address must still match a resource block you have written, so the one way to skip writing that block first is configuration generation, covered in the next section. Because the block does nothing once the object is in state, you can remove it after a successful apply[6] without destroying the resource, and the common practice is to delete import blocks once the migration is done so the configuration is not cluttered with one-time instructions.

Add an import block (to, id) terraform plan previews the import terraform apply performs the import Remove the block after apply
The import block lifecycle: add the block, preview with plan, perform with apply into state, then remove the block.

Generating configuration with -generate-config-out

Writing the resource block by hand is the tedious part of an import, especially for a resource with dozens of arguments. The config-driven path can produce a first draft for you: run terraform plan -generate-config-out=FILE[8] with an import block whose to points at a resource that does not yet exist in your configuration, and Terraform writes generated HCL for that object into the named file.

A plan-time feature of import blocks, not the CLI

Configuration generation is a feature of import blocks reached through terraform plan; the classic terraform import CLI command cannot generate configuration[4]. The FILE must be a new path: supplying a path to an existing file makes Terraform error[8] rather than overwrite your work. Once the plan has written the file, terraform apply completes the import.

Review before you apply

The output is Terraform's best guess at the value for each resource argument[8]: a template to iterate on by removing some attributes, adjusting the value of others, and rearranging blocks, not a finished module. For some resources Terraform cannot construct a valid configuration and emits blocks that need manual fixes, and not every provider supports generation. Review and edit the generated file before you apply it.

Import block, no resource config yet terraform plan -generate-config-out=FILE Review and edit the generated HCL terraform apply imports it
Generating configuration: an import block with no config yields draft HCL under -generate-config-out, which you review and edit before apply.

Exam-pattern recognition

Import questions turn on a few sharp distinctions between the two mechanisms. Match the stem to the mechanism.

  • A stem says import will write the configuration for you. That is false for the CLI: terraform import only updates state, so the resource block must already exist. Configuration generation is a feature of the config-driven import block via terraform plan -generate-config-out=FILE, not of the classic command.
  • A stem asks how to import many resources, or to keep imports in version control and code review. The answer is import blocks, not a loop of CLI commands: blocks are declarative, reviewable, and support for_each, whereas terraform import imports one object per invocation.
  • A stem swaps to and id, or asks which is which. to is the Terraform resource address; id is the provider's own identifier for the existing object, and its format is set by the resource type.
  • A stem invents a command like terraform import-gcp, or claims terraform refresh adopts unmanaged resources. Both are wrong. The only ways to bring existing infrastructure under management are terraform import ADDRESS ID and the import block; refresh only updates resources already in state.
  • A stem asks whether you can delete an import block afterward. Yes. An import block only affects state, so removing it after a successful apply does not destroy the resource.
  • A stem treats generated configuration as ready to apply. It is a starting point: review and edit it first, because generated blocks may include attributes that need adjustment to produce a clean plan.

Classic terraform import CLI vs the config-driven import block

Aspectterraform import (CLI)import block (config-driven)
Added inEarly TerraformTerraform 1.5
What it writesState onlyState only
Generates configuration?No; write the resource block firstOptionally, via terraform plan -generate-config-out
How it runsterraform import ADDRESS IDterraform plan, then terraform apply
Import many at once?No; one object per commandYes; one block each or for_each
Removable after use?Not in configurationYes; remove the block after a successful apply

Decision tree

Terraform older than 1.5?terraform import ADDRESS IDthe only pre-1.5 optionYesNoNeed Terraform to draft config?import block +-generate-config-outYesNoWant imports in code review?import blockto and id, plan then applyYesNoterraform import ADDRESS IDquick one-off import

Sharp facts the exam loves — give these one last read before exam day.

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Import requires a pre-existing resource block

Before running terraform import ADDRESS ID, you must already have written the target resource block in your configuration; the classic CLI import binds an existing object to that pre-existing address and cannot import into an address that has no matching resource block.

Trap Assuming terraform import writes the resource block for you — it only updates state, so the configuration must exist first.

4 questions test this
Two positional arguments: ADDRESS and ID

terraform import takes exactly two positional arguments, terraform import ADDRESS ID, where ADDRESS is the resource address in your configuration and ID is the provider-specific identifier of the real object; the ID format varies by resource type.

Trap Thinking the ID is a Terraform-chosen name — it is the provider's own object identifier and differs per resource type.

3 questions test this
CLI import only writes state

The terraform import CLI command only records the existing object in Terraform state; it does not generate configuration and does not create, modify, or destroy any real infrastructure.

Trap Expecting import to auto-generate HCL — the classic CLI command never generates configuration.

5 questions test this
No provider-specific import command

There is no provider-specific import command such as terraform import-gcp, and terraform refresh does not adopt unmanaged objects; the only CLI way to bring existing infrastructure under management is terraform import ADDRESS ID (or a config-driven import block).

Trap Believing a command like terraform import-gcp exists or that terraform refresh discovers and adopts unmanaged resources.

3 questions test this
import block uses to and id

The configuration-driven import block (Terraform 1.5+) declares an import with two arguments: to, the resource address to import into, and id, the provider-specific identifier of the existing object.

Trap Swapping the arguments — to is the Terraform resource address and id is the real object's provider ID, not the reverse.

7 questions test this
Import blocks run through plan and apply

An import block is executed through the normal workflow: terraform plan previews the planned import and terraform apply performs it; there is no separate import subcommand, and the id must be a literal or known value at plan time.

Trap Thinking import blocks need a special command — they are planned and applied like any other configuration change.

8 questions test this
Import blocks can be removed after apply

Because an import block only affects state (recording an existing object at its address), you can safely remove the block from configuration after a successful apply without destroying the resource.

-generate-config-out generates HCL

Running terraform plan -generate-config-out=<FILE> together with an import block generates HCL resource configuration for the imported objects into the given file; this config generation is a feature of import blocks, not of the classic terraform import CLI command.

Trap Trying to use -generate-config-out with the classic terraform import CLI command — only the config-driven import block supports generated configuration.

11 questions test this
Generated config is a starting point

Configuration produced by -generate-config-out is a starting point that you should review and edit before applying, since generated blocks may include attributes that need adjustment to produce a valid plan.

References

  1. Command: import
  2. State
  3. Resource Addressing
  4. Import (CLI)
  5. Command: refresh
  6. The import block
  7. import block reference
  8. Generating configuration