The Ultimate Guide to Terraform Import

Master Terraform import in minutes: map existing cloud resources into code, dodge common pitfalls, and streamline your IaC with this step-by-step guide.

Bringing Existing Resources Under Code Control

Terraform import bridges the gap between manually created infrastructure and infrastructure as code. This comprehensive guide provides everything you need to successfully import existing resources into Terraform, from basic concepts to advanced techniques.

What Terraform import is and when to use it

Terraform import allows you to bring existing infrastructure resources under Terraform management without rebuilding them. At its core, this functionality maps real-world resources created outside of Terraform to resource blocks in your Terraform configuration.

Two primary import methods are available:

  1. The terraform import CLI command - available in all Terraform versions
  2. The import block - introduced in Terraform v1.5.0, enabling configuration-driven imports

When you run an import operation, Terraform queries the provider's API to capture the current state of the resource and writes this information to your state file. This creates a binding between the remote object and a resource instance in your configuration.

Key use cases for Terraform import

Terraform import is particularly valuable in several common scenarios:

  • Migrating to IaC: When an organization adopts Terraform after already having existing infrastructure
  • Moving between tools: When transitioning from other IaC tools like CloudFormation or Pulumi
  • Handling emergency resources: For resources created through a cloud console during incidents
  • Disaster recovery of state: If your Terraform state file becomes corrupted or lost
  • Incremental adoption: Facilitating phased adoption of Terraform within an organization
  • Refactoring state: When splitting large state files into smaller, more manageable pieces

The fundamental limitation of Terraform import is that it only updates the state file; it doesn't automatically generate the corresponding configuration code (though newer versions provide options for this). You must still write or generate the resource configuration to match the imported state.

Step-by-step process for importing existing resources

1. Preparation phase

Before importing resources, complete these essential preparation steps:

  1. Inventory existing resources you want to bring under Terraform management
  2. Identify dependencies between resources to determine import order
  3. Verify access and permissions to view and manage target resources
  4. Install Terraform (v0.12+ for CLI import, v1.5+ for import blocks)
  5. Configure providers with necessary authentication

2. Create placeholder configuration

For each resource you plan to import, create a minimal resource block in your Terraform configuration:

resource "aws_instance" "web_server" {
  # Minimal required configuration - will be filled in later
}

The resource address (aws_instance.web_server in this example) must exactly match what you'll use in the import command.

3. Identify resource ID format

Each resource type has a specific format for its import ID. This varies by provider and resource type:

  • AWS: Often simple IDs (e.g., i-1234567890abcdef0 for EC2 instances)
  • Azure: Full resource paths (e.g., /subscriptions/{subscription-id}/resourceGroups/{resource-group}/providers/Microsoft.Compute/virtualMachines/{vm-name})
  • GCP: Various formats, often including project information (e.g., projects/{project}/zones/{zone}/instances/{instance})

Consult the provider documentation for the exact format required for each resource type.

4. Import the resource

Using CLI import command:

terraform import aws_instance.web_server i-1234567890abcdef0

Using import block (Terraform v1.5+):

import {
  to = aws_instance.web_server
  id = "i-1234567890abcdef0"
}

With import blocks, you need to run terraform plan and terraform apply to execute the import.

5. Refine configuration

After importing, run terraform plan to identify differences between your minimal configuration and the imported state:

terraform plan

Update your configuration to align with the imported state, addressing any differences Terraform identifies. Continue running terraform plan until it shows no changes.

6. Verify successful import

Once terraform plan shows no changes, your import is successful. Run a final verification with:

terraform state list # Confirm resource appears in state
terraform state show aws_instance.web_server # Review imported attributes

Best practices and common patterns

Resource organization strategy

  • 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 for better manageability

Configuration management

  • Minimal initial configuration: Start with only required attributes, then expand
  • Configuration pruning: Remove attributes managed by the provider or defaulted values
  • Version control: Commit configuration before and after import with clear change descriptions

Team coordination

  • Document imports: Maintain an import log documenting what was imported and when
  • Staged imports: For large infrastructures, import in stages across multiple planned sessions
  • Approval workflows: Implement approval processes for import operations affecting production

Automation strategies

  • Scripted imports: For bulk operations, use scripts to automate sequential imports
  • For-each import blocks: Use Terraform 1.5+ import blocks with for_each for multiple resources:
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
}

State management

  • State backup: Always back up state before large import operations
  • Remote state: Use remote state storage with locking for team environments
  • State manipulation: Use terraform state mv to reorganize resources after import

How to handle different resource types

Simple resources

For straightforward resources like storage buckets or standalone virtual machines:

  1. Create minimal configuration with required attributes
  2. Import using the resource ID
  3. Update configuration based on terraform plan output

Nested resources

For resources with child components (like AWS security groups with rules):

  1. Import the parent resource first
  2. Check if child resources were automatically imported
  3. If not, create and import child resources separately
  4. Establish proper references between parent and children

Resources with dependencies

For resources that depend on others (like EC2 instances in a VPC):

  1. Import prerequisite resources first (VPC, subnets)
  2. Reference existing resources using Terraform references or data sources
  3. Import dependent resources (instances, load balancers)

Provider-specific considerations

Each provider has unique import requirements. Examples include:

  • AWS Provider 4.0+: S3 buckets split into multiple resources that must be imported 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

Common pitfalls and troubleshooting

Frequent errors and solutions

  1. Resource not found
    • Error: Error: Cannot import non-existent remote object
    • Solution: Verify resource ID format and check permissions
  2. Missing configuration
    • Error: Resource address does not exist in the configuration
    • Solution: Create resource block before importing
  3. State lock errors
    • Error: Error acquiring the state lock
    • Solution: Check for other running Terraform processes
  4. Provider configuration issues
    • Error: Invalid provider configuration
    • Solution: Ensure provider configuration only depends on variables, not data sources
  5. Import conflicts
    • Error: Resource already exists in state
    • Solution: Remove the resource from state first or use a different address

Troubleshooting techniques

  • Iterative configuration refinement:
    1. Import resource
    2. Run terraform plan to see differences
    3. Update configuration
    4. Repeat until plan shows no changes

State inspection commands:

terraform state list
terraform state show <resource_address>

Enable debug logging:

export TF_LOG=TRACE
export TF_LOG_PATH=terraform.log

Handling dependency errors

When importing resources with dependencies:

  1. Import dependencies first
  2. Use -target flag to apply subsets of configuration
  3. For complex circular dependencies, temporarily remove references and reestablish after import

AWS Examples

EC2 Instance

# Configuration
resource "aws_instance" "app_server" {
  ami           = "ami-0abcdef1234567890"
  instance_type = "t2.micro"
}

# CLI Import
terraform import aws_instance.app_server i-1234567890abcdef0

# Import Block
import {
  to = aws_instance.app_server
  id = "i-1234567890abcdef0"
}

S3 Bucket (AWS Provider 4.0+)

# Main bucket configuration
resource "aws_s3_bucket" "data_lake" {
  bucket = "my-data-lake"
}

# Separate bucket policy configuration
resource "aws_s3_bucket_policy" "data_lake_policy" {
  bucket = aws_s3_bucket.data_lake.id
  policy = jsonencode({
    # Policy document
  })
}

# Import commands
terraform import aws_s3_bucket.data_lake my-data-lake
terraform import aws_s3_bucket_policy.data_lake_policy my-data-lake

Azure Examples

Virtual Machine

# Configuration
resource "azurerm_virtual_machine" "app_server" {
  name                  = "app-server"
  location              = "East US"
  resource_group_name   = "app-resources"
  network_interface_ids = [azurerm_network_interface.app_nic.id]
  vm_size               = "Standard_DS1_v2"
  # Other required configuration
}

# CLI Import
terraform import azurerm_virtual_machine.app_server /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/app-resources/providers/Microsoft.Compute/virtualMachines/app-server

# Import Block
import {
  to = azurerm_virtual_machine.app_server
  id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/app-resources/providers/Microsoft.Compute/virtualMachines/app-server"
}

GCP Examples

Compute Instance

# Configuration
resource "google_compute_instance" "app_server" {
  name         = "app-server"
  machine_type = "e2-medium"
  zone         = "us-central1-a"
  
  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"
    }
  }
  
  network_interface {
    network = "default"
  }
}

# CLI Import
terraform import google_compute_instance.app_server projects/my-project/zones/us-central1-a/instances/app-server

# Import Block
import {
  to = google_compute_instance.app_server
  id = "projects/my-project/zones/us-central1-a/instances/app-server"
}

Advanced import scenarios and techniques

Bulk import strategies

For importing multiple resources of the same type:

Using third-party tools like Terraformer:

# Install Terraformer
go install github.com/GoogleCloudPlatform/terraformer@latest

# Import all EC2 instances in a region
terraformer import aws --resources=ec2_instance --regions=us-west-2

Using for_each with import blocks:

locals {
  instances = {
    "web"    = "i-12345678"
    "app"    = "i-23456789"
    "db"     = "i-34567890"
  }
}

import {
  for_each = local.instances
  to       = aws_instance.servers[each.key]
  id       = each.value
}

Importing into modules

To import resources into modules:

# CLI Import into module
terraform import module.servers.aws_instance.app_server i-1234567890abcdef0

# Import block for module resource
import {
  to = module.servers.aws_instance.app_server
  id = "i-1234567890abcdef0"
}

State manipulation techniques

After importing, you can reorganize resources using state commands:

Using moved block (Terraform 1.1+):

moved {
  from = aws_instance.old_name
  to   = aws_instance.new_name
}

Moving resources between modules:

terraform state mv aws_instance.standalone module.servers.aws_instance.server

Moving resources:

terraform state mv aws_instance.old_name aws_instance.new_name

Recent updates to import functionality

Import block (Terraform v1.5+)

The import block, introduced in Terraform 1.5, provides a declarative way to define imports directly in configuration:

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

Key benefits over CLI import:

  • Preview imports during terraform plan
  • Integrate with CI/CD pipelines
  • Version control import definitions
  • Support for bulk imports using for_each

Automatic configuration generation

Terraform 1.5+ can automatically generate configuration for imported resources:

terraform plan -generate-config-out=generated.tf

This creates configuration for resources defined in import blocks but not yet in your configuration. The generated code includes all attributes and often requires pruning to remove provider-managed or default values.

Identity-based import

An alternative to ID-based import using key-value pairs to identify resources:

import {
  to = aws_dynamodb_table.example
  identity = {
    name = "my-table"
  }
}

Integration with Terraform state management

Terraform state is central to how import works:

  • State-only operation: Import only updates the state file, not the configuration
  • One-to-one mapping: Each remote resource should be imported to only one resource address
  • Complex imports: Some resources result in multiple related resources in state
  • State locking: Imports respect state locking to prevent concurrent modifications
  • State backends: Import works with all state backends (local, S3, Azure, GCS, etc.)

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 resources without destroying them
  • Removing from state: Use terraform state rm to remove resources without destroying them

How import fits into infrastructure as code workflows

Incremental adoption strategy

Import facilitates an incremental approach to IaC adoption:

  1. Assessment: Inventory existing infrastructure and dependencies
  2. Prioritization: Identify critical resources to import first
  3. Import foundation: Import base infrastructure (networking, IAM)
  4. Import applications: Import application resources
  5. Standardize: Refactor configuration to follow standards
  6. Expand coverage: Incrementally bring more resources under management

CI/CD integration

With import blocks, Terraform import integrates more seamlessly with CI/CD pipelines:

  1. Detect: Identify resources to import
  2. Configure: Generate import blocks and initial configuration
  3. Review: Create PR for review by infrastructure team
  4. Plan: Run terraform plan to preview import
  5. Apply: Execute import after approval

GitOps methodology

Import enables GitOps workflows for existing infrastructure:

  1. Repository setup: Create infrastructure repository with Terraform configurations
  2. Import process: Import existing resources into Terraform state
  3. Version control: Commit configurations to Git
  4. CI/CD integration: Set up pipelines for Terraform plan/apply
  5. Pull request workflow: All future changes go through PRs

Real-world transition strategy

A practical approach for organizations transitioning to Terraform:

  1. Start small: Begin with non-critical resources
  2. Document everything: Maintain detailed records of import process
  3. Establish standards: Create configuration standards before widespread adoption
  4. Train teams: Provide training on import process and standards
  5. Expand methodically: Gradually increase scope of Terraform-managed resources
  6. Automation: Develop scripts and workflows to streamline import process

Conclusion

Terraform import provides a crucial bridge between existing infrastructure and the infrastructure-as-code paradigm. While it has its challenges, particularly around configuration generation and complex resource relationships, the continuous improvements to import functionality—especially the introduction of import blocks in version 1.5—have made the process more streamlined and CI/CD-friendly.

By following the step-by-step processes and best practices outlined in this guide, you can successfully bring your existing infrastructure under Terraform management, unlocking the benefits of version control, reproducibility, and automation for your entire infrastructure landscape.