Guide ยท 1 article branch off this one
Terraform Import: How to Import Existing Resources Into Terraform
Use the terraform import command and import blocks to bring existing AWS, Azure, and GCP resources into Terraform state, with examples, FAQs, and bulk-import patterns.

Key takeaways
- Use import {} blocks (Terraform 1.5+) instead of the legacy terraform import CLI command: they are declarative, plan-previewable, reviewable in PRs, and work cleanly in CI/CD pipelines.
- Import is state-only. It records the resource in your state file but never generates HCL on its own and never modifies the live cloud resource; pair import blocks with terraform plan -generate-config-out=generated.tf to draft the configuration.
- The hardest part of most imports is locating the provider's required resource ID, not running the command. IDs hide in consoles, CLI output, and sometimes only in the browser URL of the resource's page.
- Always run terraform plan after importing and refine your HCL until the plan shows no changes; mismatches between configuration and the real resource surface as drift on the first apply.
- For bulk imports, use for_each with import blocks (Terraform 1.7+), aztfexport for Azure resource groups, or Terraformer for multi-cloud discovery. All generated code needs manual review before committing.
- Prefer terraform state rm and terraform state mv over hand-editing state files; manual download-edit-reupload of state is where lineage mismatches and forked state come from.
Use an import {} block (Terraform 1.5+, OpenTofu 1.6+) to bring existing cloud resources under Terraform: add the block, run terraform plan to preview, and terraform apply writes the resource into state. The older terraform import command still works but skips the preview, so keep it for one-off sandbox fixes. Neither touches the live resource, and neither writes your HCL unless you pair the block with terraform plan -generate-config-out. Below: exact commands for AWS, Azure, and GCP, bulk-import patterns, and the errors that trip people up.
What Does Terraform Import Do?
Terraform import takes an existing resource (an EC2 instance, an S3 bucket, an Azure VM, a GCS bucket, or any resource type your provider supports importing) and records it in your Terraform state file. After import, Terraform manages that resource the same as if it had been created by terraform apply from the start. You can then update it, plan changes against it, and destroy it through code.
Two important caveats up front:
- Import is state-only. It writes the resource into state but does not generate the corresponding HCL configuration (with one exception: the
-generate-config-outflag, covered below). You still have to write the matchingresourceblock by hand, or use code generation. - Import does not modify the cloud resource. It only reads it and adds an entry to state. Nothing about the live resource changes during an import.
When Do You Need Terraform Import?
- Adopting IaC on existing infra: brownfield environments where Terraform shows up after the AWS console has been doing the work for a year
- Migrating off another tool: CloudFormation, Pulumi, ARM templates, or hand-rolled scripts
- Console-created hotfixes: a resource someone spun up at 3am during an incident, now needs to live in code
- State recovery: rebuilding state after a corrupt or lost state file
- Splitting state: breaking up a monolithic state file across teams or environments
Should You Use the Import Command or the Import Block?
Use the import {} block for any new work. As of July 2026 it's the recommended method in both engines (Terraform 1.5+ and OpenTofu 1.6+): it's declarative, you preview the import with terraform plan before anything touches state, and it pairs with -generate-config-out to draft HCL. The terraform import CLI command, around since Terraform 0.7, writes to state immediately with no preview; keep it for ad-hoc, interactive fixes.
terraform import command |
import {} block |
|
|---|---|---|
| Available since | Terraform 0.7 | Terraform 1.5 / OpenTofu 1.6 |
| Style | Imperative, one resource per call | Declarative, lives in your .tf files |
| Plan preview | No, writes state immediately | Yes, shows as "to import" in terraform plan |
| Code review / CI-friendly | No | Yes, ships through a normal PR |
| Generates HCL | No | Draft via terraform plan -generate-config-out (still experimental as of July 2026) |
| Bulk imports | One at a time (scripted loops) | for_each on the block (Terraform 1.7+ / OpenTofu 1.8+) |
The CLI command is still worth knowing because most older tutorials reference it, and it remains the fastest path when you're poking at a single resource in a sandbox.
The Terraform Import Command
The terraform import command links a remote, pre-existing resource to a resource block in your Terraform configuration. It is the original Terraform import method and still works in current Terraform versions.
Terraform Import Syntax
terraform import aws_instance.example i-1234567890abcdef0The syntax is terraform import <resource_address> <remote_id>. The resource address (e.g., aws_instance.example) must already exist in your configuration; the remote ID is the cloud provider's identifier for the live resource.
Common Import Flags
Here are the most useful flags for the terraform import command:
-config=path: Specifies the path to the directory containing your Terraform configuration files-input=true/false: Determines whether Terraform should ask for interactive input (set tofalsefor automation)-lock=false: Disables state locking (generally not recommended in collaborative environments)-lock-timeout=0s: Sets a duration to retry acquiring a state lock before failing-no-color: Disables colorized output-var-file=path: Loads variable values from a.tfvarsfile (useful when your provider config references variables)-parallelism=n: Limits the number of concurrent operations (default is 10)-var 'foo=bar': Sets a variable from the command line
Terraform Import Examples
These are the most common terraform import examples by provider. Each one links a live resource to a resource block already in your configuration. The pattern is always terraform import <RESOURCE_ADDRESS> <RESOURCE_ID>, but the ID format varies by provider.
In practice, the hardest part of an import is usually locating that ID, not running the command. A community member recently wanted to import a Scalr module with terraform import scalr_module.example mod-xxxxxxxxxx and couldn't find the mod- ID anywhere in the UI; it turned out to be sitting in the browser URL of the module's page. That pattern repeats across providers: when an ID isn't surfaced in a console field, check the resource's URL, the provider's describe/show CLI output, and the provider documentation's import section (which lists the exact expected ID format for every importable resource type).
Terraform Import AWS EC2 Instance
terraform import aws_instance.web_server i-abcd1234AWS EC2 instance IDs start with i-. You can find them in the EC2 console or via aws ec2 describe-instances.
Terraform Import S3 Bucket
terraform import aws_s3_bucket.data_lake my-data-lakeNote: in AWS provider v4.0+, S3 bucket configuration was split into multiple resources (aws_s3_bucket, aws_s3_bucket_versioning, aws_s3_bucket_acl, etc.). You may need to import related S3 resources separately, depending on which parts of the bucket configuration you want Terraform to manage. Not every bucket needs every split-out resource.
Terraform Import IAM Role
terraform import aws_iam_role.lambda_execution my-lambda-execution-roleUse the role name (not the ARN) as the import ID. Attached policies and inline policies need separate imports. See streamlining AWS IAM role creation with Terraform for the full pattern.
Terraform Import RDS Instance
terraform import aws_db_instance.primary my-primary-dbTerraform Import Azure VM
Azure resource IDs are full ARM paths:
terraform import azurerm_virtual_machine.app_server /subscriptions/xxx-xxx-xxx-xxx-xxx/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVMFor importing entire Azure resource groups, aztfexport (covered below) is far faster than running individual terraform import commands.
Terraform Import GCP Compute Instance
terraform import google_compute_instance.app_server projects/my-project/zones/us-central1-a/instances/app-serverGCP IDs typically include project and zone. The simpler shorthand my-project/us-central1-a/app-server also works for most resources.
Handling Computed and Default Attributes
With terraform import, computed attributes and default settings are where things often get tricky. Computed attributes are values the cloud provider sets after the resource is created (like timestamps, default security group IDs, or ARN components).
Solution 1: Explicitly Ignoring Attributes
Use the ignore_changes lifecycle meta-argument to tell Terraform to disregard drift for specific attributes:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
lifecycle {
ignore_changes = [
default_security_group_id,
tags_all,
]
}
}Use ignore_changes sparingly. It hides drift, so it should be reserved for attributes you intentionally do not want Terraform to manage. Reaching for it on every imported resource turns it into a place where real configuration drift gets buried.
Solution 2: Including Default Values in HCL
For attributes where you can reliably determine the default value, explicitly include that value in your HCL:
resource "aws_db_instance" "example" {
# ... other imported attributes
backup_retention_period = 7 # Explicitly set the provider's default
}The Terraform Import Block
Introduced in Terraform 1.5, the import {} block is a declarative alternative to the terraform import command, integrated directly into the configuration language. You write the import as code, run terraform plan to preview it, and terraform apply to commit. The same workflow as any other Terraform change.
Basic Import Block Syntax
import {
to = aws_s3_bucket.legacy_bucket
id = "my-legacy-data-bucket"
}When you run terraform plan and terraform apply with an import block present, Terraform performs the import operation. You can also generate configuration automatically using:
terraform plan -generate-config-out=generated.tfOne gotcha on remote-execution backends: -generate-config-out writes the file to whatever machine runs the plan. If that's a remote runner, the generated .tf vanishes with the runner, so from your laptop the flag looks like it did nothing. A community member hit exactly this with tofu plan -generate-config-out and said they missed the feature dearly.
The fix is simple. Run the generation step locally against the same state, clean up the HCL, commit it, and let the remote pipeline do the actual import apply.
Import Block with for_each
Version note: Basic
import {}blocks require Terraform 1.5+.for_eachinside import blocks was added later, so verify you're on Terraform 1.7+ (or a compatible OpenTofu version) before using this pattern.
For importing multiple resources of the same type:
locals {
buckets = {
"staging" = "staging-bucket"
"uat" = "uat-bucket"
"prod" = "production-bucket"
}
}
import {
for_each = local.buckets
to = aws_s3_bucket.app_data[each.key]
id = each.value
}
resource "aws_s3_bucket" "app_data" {
for_each = local.buckets
bucket = each.value
}Import Block into Modules
To import resources into modules:
import {
to = module.servers.aws_instance.app_server
id = "i-1234567890abcdef0"
}Step-by-Step Import Block Workflow
There are two valid import-block workflows. Pick one before you start:
- You already have a matching
resourceblock in your config. Add theimport {}block, runterraform plan, refine the HCL until the plan is clean, then apply. - You don't have the resource block yet. Write only the
import {}block and runterraform plan -generate-config-out=generated.tfto have Terraform produce a draftresourceblock. Then clean up the generated HCL and apply.
The steps below show workflow #2 (generate-config-out). For workflow #1, skip Step 2 and edit your existing resource block in Step 3 instead.
Step 1: Prepare Import Block
Create an import block in your configuration specifying the resource to import:
import {
to = aws_s3_bucket.legacy_bucket
id = "my-legacy-data-bucket"
}Step 2: Generate Configuration
Run plan with configuration generation enabled:
terraform plan -generate-config-out=generated.tfTerraform will create a generated.tf file with configuration based on the live resource.
Step 3: Review and Refine Configuration
Do not blindly trust generated configuration. Review carefully:
- Clean it up: Remove computed attributes (like
arn,hosted_zone_id) and default values - Correct it: Fix any invalid syntax or errors
- Make it yours: Refactor to fit your team's standards and use variables
Example cleanup:
# BEFORE - Generated
resource "aws_s3_bucket" "legacy_bucket" {
bucket = "my-legacy-data-bucket"
bucket_domain_name = "my-legacy-data-bucket.s3.amazonaws.com" # Remove
hosted_zone_id = "Z3AQBSTGFYJSTF" # Remove
region = "us-east-1" # Remove
}
# AFTER - Cleaned
resource "aws_s3_bucket" "legacy_bucket" {
bucket = "my-legacy-data-bucket"
tags = {
Name = "Legacy Data Bucket"
Environment = "production"
}
}Step 4: Run Validation Plan
terraform planIdeally, the output should show 1 to import, 0 to change, 0 to destroy. If Terraform wants changes, adjust your HCL until the plan matches your intent. Generated config almost always needs some cleanup before the plan is clean.
Step 5: Apply the Import
terraform applyOnce approved, the resource is recorded in state. (You can still remove it later with terraform state rm if needed; nothing about the live resource changes.)
Step 6: Verify and Clean Up
Verify the import succeeded:
terraform state show aws_s3_bucket.legacy_bucketYou can now remove the import block from your configuration. It's a one-time operation.
Common Mistakes to Avoid with Import Blocks
- Blindly trusting generated code - Always review and clean up generated configuration
- Forgetting the destination resource - The destination
resourceblock must either already exist in your configuration or be generated withterraform plan -generate-config-out - Leaving import blocks in configuration - Remove them after successful import
- Wrong resource addressing - Missing array indices for
countor keys forfor_each - Using dynamic values - All import block values must be known at plan time; no data sources or computed values
Generate Terraform Code From Existing Resources
A common ask: "I have a hundred resources running in AWS already. Can Terraform read them and generate the HCL automatically?" The answer is yes, with caveats. None of these tools produces a final artifact. There are three common ways to generate Terraform code from existing resources in 2026:
1. terraform plan -generate-config-out (Built-in, Terraform 1.5+)
Write an import {} block referencing the resource, then run:
terraform plan -generate-config-out=generated.tfTerraform reads the live resource, generates a resource block matching its current attributes, and writes it to generated.tf. This is the built-in Terraform approach. It works with providers and resource types that support Terraform import, but generated configuration quality varies. Some cloud services are represented by multiple Terraform resources, so full management may require separate imports, and HashiCorp's docs are explicit that each remote object should be imported to only one resource address.
Limitations: generated HCL is often verbose. It can include provider defaults, hardcoded values, and attributes your team would normally turn into variables or references. Treat the output as a starting point, not a final artifact.
2. aztfexport (Azure-only)
For Azure, Microsoft's aztfexport tool can scan an entire resource group and generate HCL + a state file in one shot. Much faster than per-resource import calls for Azure migrations.
3. Terraformer (multi-cloud, third-party)
Terraformer, originally developed under the GoogleCloudPlatform GitHub organization, is a third-party CLI that walks AWS, GCP, Azure, Kubernetes, and ~20 other providers, generating HCL and state. It's the most automated option for full-estate bulk discovery but the generated code typically needs more cleanup than -generate-config-out, and the project is community-maintained. Verify it still supports your provider versions before relying on it.
# Example: generate Terraform for all EC2 instances in us-west-2
terraformer import aws --resources=ec2_instance --regions=us-west-2Comparison
| Tool | Scope | Code Quality | Best For |
|---|---|---|---|
terraform plan -generate-config-out |
Single resource per import block | Faithful but verbose | Per-resource imports in regular workflow |
| aztfexport | Azure resource group / query | Good, needs refactoring | Bulk Azure migration |
| Terraformer | Multi-cloud, broad discovery | Rough, needs heavy cleanup | Initial brownfield audit |
Whichever method you use, always review the generated code before committing. Code generation can include provider defaults, hardcoded values that should be variables or references, and produce a main.tf no team would write by hand.
Terraform Import Azure Resources With aztfexport
For Azure environments, Microsoft provides aztfexport (formerly aztfy), a command-line tool that scans existing Azure resources, generates the matching Terraform HCL code, and creates a state file in one pass. It's the fastest way to import existing Azure resources into Terraform at scale. For a hands-on walkthrough, see our guide to getting started with the Azure Terraform Export tool.
What is Aztfexport
Azure Export for Terraform (aztfexport) is an open-source tool from Microsoft that:
- Scans existing Azure resources
- Generates corresponding Terraform HCL code
- Creates a state file mapping to live infrastructure
- Supports both
azurermandazapiTerraform providers
Under the hood, aztfexport maps Azure resource IDs to Terraform resource types, calls Terraform import on each, and writes the generated HCL and a mapping file. For normal use you don't need to think about that.
Generating Import Blocks (Modern Workflow)
Recent versions of aztfexport (v0.13+) paired with Terraform v1.5+ can generate import {} blocks instead of running imports directly. This lets you review the imports in a PR and apply them through your normal plan/apply pipeline:
aztfexport resource-group --generate-import-block myRGThis produces an import.tf containing import {} blocks and a mapping file. Recommended for any team using Terraform 1.5 or later, since it makes the import auditable in CI/CD.
Installation
Check Microsoft's current installation docs before copying package-manager commands; the examples below show the common paths but Microsoft's repo URLs and supported distros change.
Linux (apt - Debian/Ubuntu):
curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo tee /etc/apt/trusted.gpg.d/microsoft.asc > /dev/null
sudo apt-add-repository https://packages.microsoft.com/ubuntu/20.04/prod
sudo apt-get update
sudo apt-get install aztfexportLinux/macOS (Homebrew):
brew install aztfexportWindows (winget):
winget install aztfexportBasic Commands
Export a Resource Group:
aztfexport resource-group myRGUse the -n flag for non-interactive mode with large resource groups.
Export using Azure Resource Graph Query:
aztfexport query "resourceGroup =~ 'myRG' and type =~ 'microsoft.network/virtualnetworks'"Export a Single Resource:
aztfexport resource /subscriptions/your-sub-id/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/myVMThe tool generates .tf files, a terraform.tfstate file, and a JSON mapping file (aztfexportResourceMapping.json).
Microsoft states the generated code isn't meant to be reproducible from scratch, so treat it as a draft: rename resources to your conventions, replace hardcoded IDs with references, pull values into variables, and check for exported secrets before committing.
Comparison: Aztfexport vs Terraform Import
| Feature | aztfexport | terraform import (CLI) | terraform import (Block) |
|---|---|---|---|
| HCL Code Generation | Automated | Manual | Automated |
| State Import | Automated | Automated | Automated |
| Resource Discovery | Supported (RG, Query, Interactive) | Manual | Manual identification |
| Bulk Operations | High | Low | Medium |
| Manual Effort | Medium (refinement) | Very High | Medium |
Handling Import Conflicts
Resource Already Exists in State
If a resource already exists in your state file with a different address:
# Remove from old address
terraform state rm aws_instance.old_name
# Then import to new address
terraform import aws_instance.new_name i-1234567890abcdef0Resolving Import Errors
Error: Cannot import non-existent remote object
- Verify resource ID format matches provider documentation
- Check permissions to access the resource
- Confirm the resource actually exists in the cloud provider
Error: Resource address does not exist in the configuration
- Create the resource block before importing
- Ensure the resource block address matches the import statement exactly
Error: Error acquiring the state lock
- Check for other running Terraform processes
- Verify state locking mechanism is working correctly
- Review remote backend configuration
Error: Invalid provider configuration
- Ensure provider configuration only depends on variables, not data sources
- Check authentication credentials and permissions
- Verify provider version compatibility
Lineage Mismatch: When Imported State Doesn't Belong to the Workspace
A failure mode we see regularly in Scalr's support queue when teams import or migrate whole state files between backends: plans look clean, but every apply fails with
Error: Failed to save state ... Lineage is not equal to one in workspace state.
This means that the provided state is most likely not related to the target workspace.
returned as an HTTP 412 from the backend. A team Scalr helped migrate off Terraform Cloud hit exactly this. Their custom script pulled state out of TFC into S3, then pushed it into the new workspaces. Plans succeeded, applies failed, and OpenTofu wrote an errored.tfstate with a warning that re-running apply "will create a forked state, making it harder to recover." By the time they reached us, the state had been re-imported roughly ten times during debugging, which made the lineage history even harder to reason about.
The diagnosis path that worked: run tofu state pull > current.tfstate and compare the lineage field in the pulled state against the file being pushed; if they differ, the backend is right to reject the write. The team recovered with tofu init -migrate-state and, once they had confirmed the pushed file was the authoritative one, tofu state push -force. The root cause turned out to be the homegrown download script corrupting state in transit; rewriting the script fixed it for good. The lesson: lineage checks are a guardrail, not an obstacle. Reach for -force only after you've pulled the workspace state and verified which copy is the real one.
Handling Dependency Errors
When importing resources with dependencies:
- Import dependencies first (VPC, subnets, IAM roles)
- If you must work in subsets, use
-targetonly as a temporary recovery tool, and only when you understand which dependencies you're skipping.-targetis widely overused; reach for it last, not first. - For circular dependencies, temporarily remove references and reestablish after import
Terraform Import State: How It Works
terraform import and the import {} block both do the same thing under the hood: they write the resource into your Terraform state file. The state file is what Terraform consults on every plan and apply to know what it manages. If a resource isn't in state, Terraform thinks it doesn't exist, even if it's running in your cloud account.
How State Works with Import
Import writes to state and nothing else, each remote resource should map to exactly one resource address (a few resource types import as several related entries), and imports take the normal state lock, so they work against any backend. For state file structure see Terraform state file best practices; for the remote storage that holds it, the guide to remote backends.
Remote Backend Considerations
Configure the same backend you use for normal Terraform runs before importing. Imports write to whichever backend is active after terraform init; there's no separate import-only path. For remote backends like Terraform Cloud, Scalr, or S3 with DynamoDB locking, the import inherits the backend's normal locking, RBAC, and audit controls.
State Manipulation After Import
After importing, you might need to reorganize your state:
- Splitting state: Move resources between state files using
terraform state mvwith-stateand-state-out - Renaming resources: Use
terraform state mvto rename without destroying - Removing from state: Use
terraform state rmto remove without destroying
terraform state mv aws_instance.old_name aws_instance.new_name
terraform state mv aws_instance.standalone module.servers.aws_instance.serverUse the CLI commands rather than editing the state file by hand. One Scalr customer told us the only path they had found to removing a resource from remote state was downloading the whole file, editing the JSON, and re-uploading it. They feared the procedure enough to file a feature request asking for an alternative. The alternative already exists: terraform state rm against the remote backend does the same removal atomically, with locking, and without you ever touching serial or lineage fields. Hand-edited state files are where most forked-state and lineage-mismatch incidents start.
A related locking gotcha from a state migration we worked on: the team locked the target workspace for safety during the migration, then discovered they had to fully unlock it just to run a read-only verification plan, leaving a window where an apply could have slipped through. If your backend's lock is all-or-nothing, plan the verification step into the migration window and keep it short. And if a workspace ends up stuck locked by a failed or cancelled run, you'll need to force-release the lock before any state operation proceeds.
How to Import Multiple Resources at Once (Terraform Bulk Import)
When you're migrating dozens or hundreds of existing resources into Terraform, one-at-a-time CLI imports get painful fast. There are three workable patterns for terraform bulk imports.
Bulk Import Strategies
Two of the three patterns already appear above: for_each on an import block (the for_each example) and Terraformer (under generating code from existing resources). The third is a shell loop over the CLI command, for older Terraform or when you want the imports to happen one at a time:
Scripted Bulk Imports
For large-scale imports using the CLI:
#!/bin/bash
for instance_id in i-12345678 i-23456789 i-34567890; do
terraform import aws_instance.server_${instance_id} ${instance_id}
doneResource Organization at Scale
- Logical separation: Group resources by service, application, or team
- Import order: Import foundation resources first (networking, IAM) before dependent resources
- State segmentation: Consider splitting resources across multiple state files
- Batch imports: For large infrastructures, import in stages across multiple planned sessions
Refactoring with Moved Blocks
Imported resources rarely land with the names and module structure you'd choose from scratch. Terraform's moved block (v1.1+) lets you rename or relocate them afterward without a destroy and recreate; the moved blocks guide covers renaming, moving into modules, and converting count to for_each.
Common Pitfalls and Troubleshooting
Troubleshooting Techniques
Enable debug logging:
export TF_LOG=TRACE
export TF_LOG_PATH=terraform.logInspect state:
terraform state list
terraform state show <resource_address>Validate configuration:
terraform validate
terraform fmtDon't take "corrupted state" errors at face value. In May 2026, a team running OpenTofu saw every one of their workspaces fail simultaneously with "terraform.tfstate" is corrupted. 'serial'. Manual inspection of the state files showed nothing wrong: "terraform_version": "1.11.7", "serial": 2, valid lineage, valid JSON. The actual cause was that OpenTofu 1.12 had changed the state file format and the platform's state parser choked on it. Pinning workspaces back to 1.11.8 was the immediate workaround; the platform fix shipped three days later. When a "corrupted" error appears across many workspaces at once, especially right after a Terraform or OpenTofu version bump, suspect a format/version mismatch between the tool and whatever parses your state, and check the file yourself before attempting any state surgery.
When Not to Use Import
Consider alternatives when:
- Resources are easily recreatable: If the resource is simple to recreate from scratch
- Core infrastructure with high risk: Importing networking or IAM can be risky; consider creating parallel resources
- Uncertain existing configuration: If you don't fully understand the resource configuration, importing can lead to unexpected changes
Provider-Specific Considerations
Each provider has unique import requirements:
- AWS Provider 4.0+: S3 buckets split into multiple resources; must import separately
- Azure: Resources require full ARM IDs with proper escaping in PowerShell
- GCP: Resources may need project ID in import identifier even if set in provider
- Kubernetes: Import support depends on the specific Kubernetes provider resource. Check the provider docs for the exact import ID format and whether the resource supports import at all
Running Imports in CI/CD
Import works best as a controlled migration workflow: inventory the resources, write or generate the HCL, review the plan, apply the import, then refactor naming and module structure over time. For CI/CD specifically, prefer import {} blocks over the CLI command. They can be reviewed in a pull request and executed through the same plan/apply pipeline as any other Terraform change, with the same approvals and audit trail. The CLI terraform import command bypasses your pipeline entirely and modifies state out of band, which is usually what you don't want. A managed Terraform automation platform gives those imports a consistent plan/apply pipeline with approvals, RBAC, and an audit trail out of the box.
Security Considerations for Imported Resources
Assume imported state contains secrets (passwords, connection strings, generated keys). sensitive = true only hides values in plan output, not in the state file, so restrict and encrypt remote state, never commit generated files that carry credentials, and gate who can run imports the way you gate any state-mutating action, with the import logged through CI or the backend so you can answer who imported what and when.
Terraform Import FAQ
What happens if I import a resource that's already in state?
Terraform will refuse and return an error: "Resource already managed by Terraform." If you need to move a resource to a new address, use terraform state mv instead of importing. If you need to re-import a resource (rare), first remove it from state with terraform state rm, then import to the new address.
Can I remove the import block after importing?
Yes. Once terraform apply completes successfully and the resource is in state, the import {} block is no longer doing anything. You can remove it from your configuration. The resource block stays.
Does Terraform import work with remote backends like Terraform Cloud or Scalr?
Yes. Import works with every Terraform backend: local, S3, GCS, Azure Blob, Terraform Cloud, Scalr, etc. For remote backends, import operations are executed against the remote state with the backend's standard locking and authorization rules. If you're running imports through a CI/CD pipeline, the import {} block approach is preferable since it integrates cleanly with the plan/apply workflow your remote backend already runs. If you're weighing those backends, compare them in Scalr vs Terraform Cloud.
Wrapping Up
For one-off imports, the terraform import CLI command is fine. For anything you'd want to review, automate, or repeat, use an import {} block. Generate a draft of the HCL with -generate-config-out (or aztfexport --generate-import-block on Azure), clean it up by hand, get a clean plan, and apply.
The piece most people skip and regret: after the import succeeds, refactor. Rename resources to your team's conventions, move them into modules, and use moved blocks so you don't destroy and recreate anything along the way.
A cost note for bulk imports: on a per-run platform like Scalr, an import lands as a normal plan and apply, billed as one run, with no charge for the resources you bring under management. On resource-based pricing, every imported resource raises the bill from that point on.
Frequently asked questions
What does Terraform import do?
It reads an existing remote resource and writes an entry for it into your Terraform state file. The cloud resource itself is not modified, and no HCL configuration is generated by the import alone. You write the resource block by hand or generate a draft with terraform plan -generate-config-out.
What's the difference between the terraform import command and the import block?
The terraform import CLI command is imperative: it writes to state directly with no plan preview. The import {} block (Terraform 1.5+) is declarative: you add it to your .tf files, preview the import with terraform plan, and commit it with terraform apply. The block is reviewable in PRs, CI/CD-friendly, and pairs with -generate-config-out to draft HCL, so it is the recommended approach.
Can Terraform import multiple resources at once?
Yes. Use import {} blocks with for_each (Terraform 1.7+) for many resources of the same type, aztfexport for bulk Azure imports of entire resource groups, or Terraformer for multi-cloud bulk discovery. Scripted import-block generation beats one-at-a-time CLI calls for large migrations.
Does Terraform import generate code?
Not by itself. The legacy CLI command only writes state. With an import {} block you can run terraform plan -generate-config-out=generated.tf to produce a draft resource block from the live resource, but treat it as a starting point: it includes computed attributes and provider defaults you'll want to strip before committing.
What happens if I import a resource that's already in state?
Terraform refuses with a 'Resource already managed by Terraform' error. To move a resource to a new address, use terraform state mv instead. To re-import to a different address, first remove the old entry with terraform state rm, then import.
Does Terraform import work with remote backends like Terraform Cloud or Scalr?
Yes, import works with every backend (local, S3, GCS, Azure Blob, Terraform Cloud, Scalr) and inherits the backend's locking and authorization rules. One caveat: terraform plan -generate-config-out writes the generated file to the machine running the plan, so on a remote-execution backend the file lands on the ephemeral runner, not your local repo. Run the generation step locally.
About the author

CEO at Scalr
Sebastian Stadil is the CEO of Scalr with 15+ years of DevOps experience. He started with AWS in 2004 and advised early Microsoft Azure and Google Cloud.