Terraform Import and State Management

Terraform state management is critical for maintaining consistency between your infrastructure code and real-world resources. Whether you're importing existing infrastructure, migrating backends, or manipulating state directly, mastering these techniques allows you to manage complex production environments without downtime or drift.

Prerequisites

  • Terraform 1.0+ installed
  • Access to a cloud provider (AWS, GCP, Azure) or local resources
  • Backend storage configured (S3, GCS, or local)
  • Basic familiarity with Terraform HCL syntax

Understanding Terraform State

Terraform stores the mapping between your configuration and real infrastructure in a state file (terraform.tfstate). This file tracks resource IDs, attributes, and dependencies.

# View current state
terraform state list

# Show details for a specific resource
terraform state show aws_instance.web

# Pull state from remote backend
terraform state pull > backup.tfstate

The state file is JSON and contains sensitive data — never commit it to version control.

Importing Existing Resources

Use terraform import to bring existing infrastructure under Terraform management without destroying and recreating it.

# Basic import syntax
terraform import <resource_type>.<resource_name> <resource_id>

# Import an AWS EC2 instance
terraform import aws_instance.web i-0abc123def456789

# Import an AWS S3 bucket
terraform import aws_s3_bucket.assets my-production-bucket

# Import an Azure resource group
terraform import azurerm_resource_group.main /subscriptions/SUB_ID/resourceGroups/my-rg

# Import a GCP compute instance
terraform import google_compute_instance.vm projects/PROJECT/zones/us-central1-a/instances/my-vm

Before importing, write the resource block in your configuration:

# main.tf - write this BEFORE running terraform import
resource "aws_instance" "web" {
  # Leave empty initially; terraform will populate after import
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.medium"

  tags = {
    Name = "web-server"
  }
}

After importing, run terraform plan to reconcile the configuration with actual state:

# Import the resource
terraform import aws_instance.web i-0abc123def456789

# Check what needs to be updated
terraform plan

# Adjust your HCL to match actual resource attributes
# Then verify no changes are needed
terraform plan  # Should show "No changes"

Bulk Import with import blocks (Terraform 1.5+)

# import.tf
import {
  to = aws_instance.web
  id = "i-0abc123def456789"
}

import {
  to = aws_s3_bucket.logs
  id = "my-logs-bucket"
}
# Generate configuration automatically
terraform plan -generate-config-out=generated.tf

State Manipulation Commands

Moving Resources

Use terraform state mv when renaming resources or moving them into modules:

# Rename a resource
terraform state mv aws_instance.old_name aws_instance.new_name

# Move resource into a module
terraform state mv aws_instance.web module.servers.aws_instance.web

# Move resource out of a module
terraform state mv module.servers.aws_instance.web aws_instance.web

# Move between state files (cross-project)
terraform state mv -state=source.tfstate -state-out=dest.tfstate \
  aws_instance.web aws_instance.web

Removing Resources from State

Use terraform state rm to stop managing a resource without destroying it:

# Remove a single resource from state
terraform state rm aws_instance.web

# Remove all resources in a module
terraform state rm module.servers

# Remove multiple resources at once
terraform state rm aws_instance.web aws_security_group.web_sg

Replacing State Manually

# Push a modified state file
terraform state push modified.tfstate

# Force push (use with extreme caution)
terraform state push -force modified.tfstate

Backend Configuration and Migration

Configuring Remote Backends

# backend.tf - S3 backend
terraform {
  backend "s3" {
    bucket         = "my-terraform-state"
    key            = "prod/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "terraform-locks"
  }
}
# GCS backend
terraform {
  backend "gcs" {
    bucket = "my-terraform-state"
    prefix = "prod"
  }
}

Migrating Between Backends

# Step 1: Update backend configuration in main.tf
# Step 2: Initialize with migration flag
terraform init -migrate-state

# Terraform will prompt to copy existing state to new backend
# Answer "yes" to confirm migration

# Step 3: Verify state is in new backend
terraform state list

# Step 4: Remove old backend files if migrating from local
rm terraform.tfstate terraform.tfstate.backup

State Locking

State locking prevents concurrent operations from corrupting state. With S3 + DynamoDB:

# Create DynamoDB table for locking (AWS CLI)
aws dynamodb create-table \
  --table-name terraform-locks \
  --attribute-definitions AttributeName=LockID,AttributeType=S \
  --key-schema AttributeName=LockID,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

# Force unlock if a lock is stuck (get lock ID from error message)
terraform force-unlock LOCK_ID

# Disable locking for a single operation (emergency use only)
terraform apply -lock=false

Workspace Management

Workspaces allow multiple state files within a single backend — useful for staging/production separation:

# List workspaces
terraform workspace list

# Create a new workspace
terraform workspace new staging

# Switch to a workspace
terraform workspace select production

# Show current workspace
terraform workspace show

# Delete a workspace (must switch away first)
terraform workspace select default
terraform workspace delete staging

Use workspace name in configuration:

locals {
  env = terraform.workspace

  instance_type = {
    default    = "t3.small"
    staging    = "t3.medium"
    production = "t3.large"
  }
}

resource "aws_instance" "web" {
  instance_type = local.instance_type[local.env]
}

Detecting and Resolving Drift

Infrastructure drift occurs when real resources differ from state. Use terraform refresh and plan:

# Refresh state to reflect real infrastructure (deprecated in 1.x)
terraform refresh

# Preferred: use plan with refresh
terraform plan -refresh=true

# Skip refresh for speed (when you know state is current)
terraform plan -refresh=false

# Apply only a refresh (update state without changing infrastructure)
terraform apply -refresh-only

# Target specific resources for plan/apply
terraform plan -target=aws_instance.web
terraform apply -target=aws_instance.web

# Detect all drifted resources
terraform plan | grep -E "will be|must be|has been"

Troubleshooting

State lock errors:

# Get the lock ID from the error message, then force unlock
terraform force-unlock <LOCK_ID>

# Check DynamoDB for stuck locks
aws dynamodb scan --table-name terraform-locks

"Resource already exists" during import:

# The resource may already be in state under a different name
terraform state list | grep <resource_type>
# Remove old reference if it's stale
terraform state rm <old_resource_reference>

State file corruption:

# Restore from backup
cp terraform.tfstate.backup terraform.tfstate

# Or pull from remote and inspect
terraform state pull | python3 -m json.tool | head -50

Backend initialization failures:

# Reconfigure backend without migrating
terraform init -reconfigure

# Upgrade providers and backend
terraform init -upgrade

Conclusion

Terraform state management is the backbone of reliable infrastructure-as-code workflows. By mastering import for onboarding existing resources, state manipulation commands for refactoring, and backend migration for team collaboration, you can maintain full control over complex multi-environment deployments. Always back up your state before performing destructive operations and use remote backends with locking for any production workload.