Study guide
12.1 Import Existing Infrastructure
HashiCorp Certified: Terraform Associate 004
Track your progress as you read
Finished sections get a check mark.
1 Introduction & Exam Overview
1.1 Current Exam Facts 1.2 Format, Delivery, Retakes, and Recertification 1.3 004 Changes, Terraform 1.12, and Study Plan
2 Infrastructure as Code with Terraform
2.1 What Infrastructure as Code Is 2.2 Advantages of IaC Patterns 2.3 Multi-Cloud, Hybrid, and Service-Agnostic Workflows
3 Terraform Fundamentals: Providers & State
3.1 Install and Version Terraform Providers 3.2 How Terraform Uses Providers 3.3 Configuration with Multiple Providers 3.4 How Terraform Uses and Manages State
4 Core Terraform Workflow: Init & Validate
4.1 The Terraform Write–Plan–Apply Workflow 4.2 Initialize a Working Directory 4.3 Validate a Configuration
5 Plan, Apply, Destroy, and Formatting
5.1 Generate and Review an Execution Plan 5.2 Apply Changes to Infrastructure 5.3 Destroy Terraform-Managed Infrastructure 5.4 Formatting and Style Adjustments
6 Resources, Data Sources, and References
6.1 Resource Blocks vs Data Blocks 6.2 Resource Attributes and Cross-Resource References 6.3 Resource Addressing and Interpolation Patterns
7 Variables, Outputs, and Complex Types
7.1 Input Variables, tfvars, and Precedence 7.2 Output Values 7.3 Complex Types: list, map, object, tuple, set
8 Dynamic Configuration and Dependencies
8.1 Expressions, count, for_each, and dynamic Blocks 8.2 Built-in Functions 8.3 Dependencies, depends_on, and Lifecycle Rules
9 Conditions, Secrets, and Sensitive Data
9.1 Custom Conditions: validation, precondition, postcondition, check 9.2 Sensitive Data Handling 9.3 Vault, Ephemeral Values, and Write-Only Arguments
10 Terraform Modules
10.1 How Terraform Sources Modules 10.2 Variable Scope within Modules 10.3 Using Modules in Configuration 10.4 Managing Module Versions
11 Terraform State Management
11.1 The Local Backend 11.2 State Locking 11.3 Remote State and the backend Block 11.4 Resource Drift and State Operations
12 Maintain Infrastructure with Terraform
12.1 Import Existing Infrastructure 12.2 Inspect State with the CLI 12.3 Verbose Logging and Diagnostics
13 HCP Terraform
13.1 Create Infrastructure with HCP Terraform 13.2 Collaboration and Governance Features 13.3 Workspaces and Projects 13.4 HCP Terraform Integrations
12.1 Import Existing Infrastructure
Key Takeaways Explain This Topic AI
An import block (Terraform 1.5+) with to plus id is planned as an import, then apply binds the existing object into state without creating it
Terraform 1.12 adds an identity argument on import blocks; it is mutually exclusive with id and uses provider-defined keys
Legacy terraform import ADDRESS ID writes state immediately and does not generate configuration
After import, the resource block must match the live object or the next plan will change that object
terraform plan -generate-config-out writes an experimental best-guess HCL template for import targets that lack a resource block; review it before apply
Last updated: August 2026
12.1 Import Existing Infrastructure
Quick Answer: Write a resource block for the already-existing object. Add an import block whose to address matches that resource and whose id (or, on Terraform 1.12, identity ) names the live object. Run terraform plan , fix any proposed updates until the plan is import-only, then terraform apply . The legacy terraform import ADDRESS ID command still works: it writes state immediately and does not generate configuration.
Objective 7a on Terraform Associate (004) is: import existing infrastructure into your Terraform workspace on Terraform 1.12 . HashiCorp's current language docs treat the import block as the reviewable, pipeline-friendly path. The CLI command remains valid and is still tested as the older one-shot path.
Official references: Import resources overview , import block reference , Import a single resource , Generating configuration , and terraform import .
Why this objective appears on 004
Most brownfield incidents are import mistakes, not missing resource syntax. Someone runs terraform import with no matching HCL and the next plan destroys or recreates the object. Someone imports the same EC2 instance to two addresses. Someone treats -generate-config-out as a finished module. 7a checks that you can bind an already-created object into state, keep configuration aligned, and not invent a create/destroy.
What import actually does
Import does not create the remote object. The bucket, instance, or zone already exists. Terraform asks the provider to read that object, then records it in state at a resource address you choose. From that moment the object is managed: later plans compare configuration to that state and to the live API.
HashiCorp's rule: each remote object should be bound to one resource address. Importing the same object twice (two addresses, or two workspaces fighting over one ID) produces unwanted behavior because Terraform assumes it created every object it tracks.
Not every resource type is importable. The provider must implement import. The Registry page for that resource documents the ID format and whether import is supported. If import is missing, that is a provider limitation, not a missing Terraform CLI flag.
Modern path: the import block (Terraform 1.5+, current on 1.12)
Add an import block anywhere in the root module. HashiCorp recommends either an imports.tf file or placing each block next to its destination resource block. import {
- to = aws_s3_bucket.logs
- id = "app-logs-004-example"
- }
- resource "aws_s3_bucket" "logs" {
- bucket = "app-logs-004-example"
- }
Arguments you must know for 004:
- Argument Required? Role on Terraform 1.12
- to Yes Destination address ( TYPE.LABEL , plus an index if count / for_each , or module.NAME.... )
- id One of id or identity Provider-specific import ID; must be known at plan time
identity One of id or identity New in 1.12.0. Object of provider-defined keys that uniquely identify the resource. Mutually exclusive with id
- for_each No Import many similar objects from a map or set
- provider No Non-default provider configuration (alias) used for the import read
The destination resource block must exist (unless you are using experimental config generation — below). Its type and label form the state address. You do not have to copy every attribute the provider stores in state, but you do have to set the arguments that differ from provider defaults. HashiCorp is explicit: omitted arguments take defaults; if the live object is not at those defaults, the next plan updates the object.
Terraform 1.12 identity (do not overclaim)
The 1.12.0 changelog (14 May 2025) adds identity on import blocks. It is an object, not a string, and it cannot appear in the same block as id . Keys and values come from that resource's provider documentation , not from a universal Terraform schema. Some types still import only with id (classic example: many compute instances use a single provider ID such as i-abcd1234 ). Some types can be identified by a set of attributes instead of that string. On the exam, pick identity only when the question says the provider documents an identity object. Do not invent keys. import {
- to = aws_s3_bucket.logs
- identity = {
- # keys are provider-defined — read the resource's import docs
- }
- }
- Plan, then apply terraform plan
- terraform apply
plan prints something like Plan: 1 to import, 0 to add, 0 to change, 0 to destroy when configuration already matches. If you also see changes, edit the resource block until those updates disappear (or until the remaining updates are ones you actually want). Then apply records the import. Terraform notes that it imported the object; it did not create it.
Import blocks are idempotent . After the object is in state at that address, another plan does not import it again. You may delete the import block or leave it as a historical record. HashiCorp documents both; leaving it is useful for later maintainers.
- Addresses for indexed resources: import {
- to = aws_instance.web[0]
- id = "i-abcd1234"
- }
- import {
- to = aws_instance.web["env"]
- id = "i-abcd1234"
- }
- import {
- to = module.instances.aws_instance.example
- id = "i-abcd1234"
- }
for_each on the import block can walk a map of names to IDs and write to = aws_s3_bucket.this[each.key] with id = each.value . The destination resource block needs a matching for_each .
Loading diagram...
- Legacy path: terraform import ADDRESS ID
- Usage: terraform import [options] ADDRESS ID . terraform import aws_instance.foo i-abcd1234
- terraform import module.foo.aws_instance.bar i-abcd1234
- terraform import 'aws_instance.baz[0]' i-abcd1234
- terraform import 'aws_instance.baz["example"]' i-abcd1234
Before this command you must write the resource block. The command finds the remote object from ID and writes it into state at ADDRESS . Official docs: importing via the CLI does not generate configuration. If you want generated HCL, use an import block plus -generate-config-out , not this command.
The CLI path is immediate. There is no separate apply of an import action — the state file (local or remote) is updated when the command succeeds. That is why teams prefer the block: reviewers see 1 to import on a normal plan. HashiCorp also notes that on HCP Terraform the import command runs locally , so it does not automatically see remote workspace variables. Set equivalent local variables or use the import block inside a remote run instead.
Useful contrast for 004:
- import block + plan/apply terraform import ADDRESS ID
- When it landed Terraform 1.5 language Long-standing CLI
- Writes state On apply after a reviewed plan Immediately on success
- Generates HCL? Only if you add experimental -generate-config-out Never
- Many resources Multiple blocks or for_each One address per invocation
- CI / review Same workflow as other changes One-shot, easy to skip review
- HCP Terraform Import action can run in the remote plan/apply Command runs on the laptop
- Configuration generation — what 1.12 actually supports
Do not claim that import writes a complete production module by itself. Official 1.12 support is this experimental feature (present since 1.5, still labeled experimental in HashiCorp docs): terraform plan -generate-config-out=generated.tf
Rules from the generating-configuration page:
You still write the import block ( to + id / identity ).
If that address has no resource block yet, Terraform writes a best-guess HCL template to a new file path. An existing file path is an error.
The file is a starting point. HashiCorp tells you to remove arguments, adjust values, and move blocks into modules before commit.
Complex schemas can emit conflicting arguments (two mutually exclusive fields both populated). Terraform may still write the file; you delete one argument and plan again.
The plan output still warns that config generation is experimental and that formatting may change.
That is the whole 1.12 generate story. Later product lines added query/list bulk search ( terraform query , .tfquery.hcl ) as a separate workflow; 004 / Terraform 1.12 does not require you to treat bulk search as the default import path. Do not answer 7a with terraform query .
After import, configuration must match
Import copies current remote attributes into state. The next plan compares your resource block to that state and to a refresh. If you imported a bucket named app-logs-004-example but the block sets bucket = "something-else" , Terraform plans a change (often a force-new). If you omit a non-default argument, Terraform plans to set the default. Empty-ish resource blocks are a common way to accidentally "fix" a live object.
Workflow that 004 wants:
Confirm the provider documents import and the ID (or identity) format.
Write the resource block with the arguments you intend to manage.
Add the import block (or run the CLI command).
terraform plan until the import is clean — or until remaining changes are deliberate.
terraform apply (block path) or accept that the CLI already wrote state.
Commit the reviewed HCL. Do not commit a raw experimental dump you never read.
Scenario: the console-created bucket
Ava created app-logs-004-example in the AWS console last year. The team now wants it in the logging workspace. She adds resource "aws_s3_bucket" "logs" plus an import block with id = "app-logs-004-example" . terraform plan shows 1 to import and two in-place updates because versioning and tags in the console do not match omitted defaults. She copies those arguments into the resource block. The next plan is import-only. apply binds the bucket. She leaves the import block in imports.tf as a record. She does not run terraform import a second time to a different address.
004 traps for objective 7a
Import does not create or destroy the remote object; it binds it.
CLI import does not generate HCL. The import block plus experimental -generate-config-out is the generate path, and the output is a template.
After import, mismatched configuration will change the object on the next plan/apply.
identity is a 1.12 alternative to id , not a second required argument.
One remote object, one address.
terraform query / bulk list files are later than the 1.12 exam surface; do not overclaim them.