TrademarkTrademark
Features
Documentation
  1. Learning Center
  2. Guides

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.

Terraform Import: How to Import Existing Resources Into Terraform

Key takeaways

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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:

  1. Import is state-only. It writes the resource into state but does not generate the corresponding HCL configuration (with one exception: the -generate-config-out flag, covered below). You still have to write the matching resource block by hand, or use code generation.
  2. 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-1234567890abcdef0

The 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 to false for 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 .tfvars file (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-abcd1234

AWS 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-lake

Note: 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-role

Use 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-db

Terraform 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/myVM

For 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-server

GCP 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.tf

One 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_each inside 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:

  1. You already have a matching resource block in your config. Add the import {} block, run terraform plan, refine the HCL until the plan is clean, then apply.
  2. You don't have the resource block yet. Write only the import {} block and run terraform plan -generate-config-out=generated.tf to have Terraform produce a draft resource block. 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.tf

Terraform 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:

  1. Clean it up: Remove computed attributes (like arn, hosted_zone_id) and default values
  2. Correct it: Fix any invalid syntax or errors
  3. 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 plan

Ideally, 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 apply

Once 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_bucket

You can now remove the import block from your configuration. It's a one-time operation.

Common Mistakes to Avoid with Import Blocks

  1. Blindly trusting generated code - Always review and clean up generated configuration
  2. Forgetting the destination resource - The destination resource block must either already exist in your configuration or be generated with terraform plan -generate-config-out
  3. Leaving import blocks in configuration - Remove them after successful import
  4. Wrong resource addressing - Missing array indices for count or keys for for_each
  5. 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.tf

Terraform 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-2

Comparison

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 azurerm and azapi Terraform 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 myRG

This 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 aztfexport

Linux/macOS (Homebrew):

brew install aztfexport

Windows (winget):

winget install aztfexport

Basic Commands

Export a Resource Group:

aztfexport resource-group myRG

Use 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/myVM

The 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-1234567890abcdef0

Resolving 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:

  1. Import dependencies first (VPC, subnets, IAM roles)
  2. If you must work in subsets, use -target only as a temporary recovery tool, and only when you understand which dependencies you're skipping. -target is widely overused; reach for it last, not first.
  3. 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 mv with -state and -state-out
  • Renaming resources: Use terraform state mv to rename without destroying
  • Removing from state: Use terraform state rm to remove without destroying
terraform state mv aws_instance.old_name aws_instance.new_name
terraform state mv aws_instance.standalone module.servers.aws_instance.server

Use 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}
done

Resource 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.log

Inspect state:

terraform state list
terraform state show <resource_address>

Validate configuration:

terraform validate
terraform fmt

Don'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

Sebastian Stadil

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.

In this guide

1 articles