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:
- The
terraform import
CLI command - available in all Terraform versions - 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:
- Inventory existing resources you want to bring under Terraform management
- Identify dependencies between resources to determine import order
- Verify access and permissions to view and manage target resources
- Install Terraform (v0.12+ for CLI import, v1.5+ for import blocks)
- 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:
- Create minimal configuration with required attributes
- Import using the resource ID
- Update configuration based on
terraform plan
output
Nested resources
For resources with child components (like AWS security groups with rules):
- Import the parent resource first
- Check if child resources were automatically imported
- If not, create and import child resources separately
- Establish proper references between parent and children
Resources with dependencies
For resources that depend on others (like EC2 instances in a VPC):
- Import prerequisite resources first (VPC, subnets)
- Reference existing resources using Terraform references or data sources
- 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
- Resource not found
- Error:
Error: Cannot import non-existent remote object
- Solution: Verify resource ID format and check permissions
- Error:
- Missing configuration
- Error:
Resource address does not exist in the configuration
- Solution: Create resource block before importing
- Error:
- State lock errors
- Error:
Error acquiring the state lock
- Solution: Check for other running Terraform processes
- Error:
- Provider configuration issues
- Error:
Invalid provider configuration
- Solution: Ensure provider configuration only depends on variables, not data sources
- Error:
- Import conflicts
- Error:
Resource already exists in state
- Solution: Remove the resource from state first or use a different address
- Error:
Troubleshooting techniques
- Iterative configuration refinement:
- Import resource
- Run
terraform plan
to see differences - Update configuration
- 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:
- Import dependencies first
- Use
-target
flag to apply subsets of configuration - For complex circular dependencies, temporarily remove references and reestablish after import
Examples with popular providers
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:
- Assessment: Inventory existing infrastructure and dependencies
- Prioritization: Identify critical resources to import first
- Import foundation: Import base infrastructure (networking, IAM)
- Import applications: Import application resources
- Standardize: Refactor configuration to follow standards
- Expand coverage: Incrementally bring more resources under management
CI/CD integration
With import blocks, Terraform import integrates more seamlessly with CI/CD pipelines:
- Detect: Identify resources to import
- Configure: Generate import blocks and initial configuration
- Review: Create PR for review by infrastructure team
- Plan: Run terraform plan to preview import
- Apply: Execute import after approval
GitOps methodology
Import enables GitOps workflows for existing infrastructure:
- Repository setup: Create infrastructure repository with Terraform configurations
- Import process: Import existing resources into Terraform state
- Version control: Commit configurations to Git
- CI/CD integration: Set up pipelines for Terraform plan/apply
- Pull request workflow: All future changes go through PRs
Real-world transition strategy
A practical approach for organizations transitioning to Terraform:
- Start small: Begin with non-critical resources
- Document everything: Maintain detailed records of import process
- Establish standards: Create configuration standards before widespread adoption
- Train teams: Provide training on import process and standards
- Expand methodically: Gradually increase scope of Terraform-managed resources
- 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.