Reverse Engineering Existing Cloud Infrastructure into Terraform
Most organizations don't start with Infrastructure as Code. They start by clicking through the AWS console, running CLI commands, or using ad-hoc scripts. Eventually, they want the benefits of Terraform—version control, repeatability, collaboration—but their infrastructure already exists.
Converting existing cloud resources into Terraform code is a common challenge. This guide covers the approaches, their trade-offs, and practical patterns for doing it effectively.
Why This Is Harder Than It Looks
At first glance, the problem seems straightforward: read what exists in the cloud, write it as Terraform code. In practice, several factors make this difficult:
- Resource relationships - A VPC has subnets, route tables, security groups, and gateways. These all reference each other. Getting the dependency graph right matters.
- Implicit vs explicit configuration - Many resources have defaults that the cloud provider applies automatically. Should your Terraform code include these or omit them?
- State management - Terraform needs to know the resource IDs to manage them. Just having the code isn't enough—you need proper state.
- Provider versions - Cloud APIs change. The attributes available in older Terraform provider versions differ from current ones.
Approach 1: Manual Import and Reverse Engineering
The traditional approach uses Terraform's built-in import command:
# Import an existing resource
terraform import aws_instance.web i-1234567890abcdef0
# Then run plan to see what Terraform thinks the config should be
terraform plan
After importing, you manually write the Terraform configuration to match, iterating until terraform plan shows no changes. This works but is tedious:
- You need to know every resource ID upfront
- You write all the HCL by hand
- Complex resources require many iterations to get right
- No visibility into resources you don't know about
Approach 2: terraform import with Generated Config (Terraform 1.5+)
Terraform 1.5 introduced the ability to generate configuration during import:
# imports.tf
import {
to = aws_instance.web
id = "i-1234567890abcdef0"
}
import {
to = aws_security_group.web_sg
id = "sg-0123456789abcdef0"
}
# Generate the configuration
terraform plan -generate-config-out=generated.tf
This generates HCL for you, which is a significant improvement. However, you still need to:
- Know all the resource IDs to import
- Manually create import blocks for each resource
- Clean up the generated code (it's often verbose)
- Organize resources into logical files
Approach 3: Cloud Provider Export Tools
Some cloud providers offer native export capabilities:
- Azure - Export template from resource groups (ARM/Bicep, not Terraform)
- GCP - Config Connector can export resources
- AWS - CloudFormation drift detection (not export to Terraform)
These tools are limited because they export to the provider's native format, not Terraform.
Approach 4: Third-Party Import Tools
Several tools automate the discovery and code generation process. The general workflow is:
- Scan your cloud account for resources
- Generate Terraform code for discovered resources
- Generate or import Terraform state
# Example: scanning an AWS account
# (syntax varies by tool)
cloud-to-terraform scan --provider aws --region us-east-1 --output ./terraform
Good tools will:
- Discover resources you didn't know about
- Handle resource dependencies automatically
- Generate idiomatic HCL (not just dumping all attributes)
- Organize output into logical modules
- Create valid Terraform state
What Makes Generated Code "Good"?
Not all generated Terraform code is equal. Quality indicators:
- References, not hardcoded values - Security groups should reference VPC IDs, not have them hardcoded
- Omitted defaults - Don't include
enable_dns_support = trueif that's the default - Consistent naming - Resource names follow a pattern
- Logical grouping - Network resources together, compute together, etc.
- No computed attributes - Output values like ARNs shouldn't be in the configuration
Compare these two representations of the same security group:
# Poor: hardcoded values, includes defaults and computed attributes
resource "aws_security_group" "sg_0abc123" {
name = "web-sg"
description = "Managed by Terraform"
vpc_id = "vpc-123456"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = ""
ipv6_cidr_blocks = []
prefix_list_ids = []
security_groups = []
self = false
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
arn = "arn:aws:ec2:us-east-1:123456789:security-group/sg-0abc123"
owner_id = "123456789"
}
# Good: uses references, omits defaults and computed values
resource "aws_security_group" "web" {
name = "web-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
The State Problem
Generating Terraform code is only half the problem. Terraform needs state to manage resources. Without state, Terraform will try to create new resources instead of managing existing ones.
Options for state:
- Import after generating code - Use
terraform importfor each resource (tedious but works) - Generate state alongside code - Some tools create valid state files
- Use import blocks - Terraform 1.5+ handles import as part of apply
Practical Workflow
A realistic workflow for converting infrastructure:
- Inventory - Scan to discover what exists. You'll likely find resources you forgot about.
- Prioritize - Start with foundational resources (VPCs, subnets) before dependent ones (EC2 instances).
- Generate - Create initial Terraform code using your chosen approach.
- Review and refactor - Clean up generated code, add variables, organize into modules.
- Import state - Get Terraform managing the resources.
- Validate - Run
terraform planand verify no changes. - Iterate - Move to the next set of resources.
When Not to Import
Sometimes creating new resources alongside existing ones is better than importing:
- Messy legacy config - If existing resources have accumulated cruft, it might be cleaner to build new
- Major architecture changes - Migrating to a new structure anyway? Build the new way.
- Disposable resources - Stateless compute instances might be easier to recreate
Conclusion
Converting existing cloud infrastructure to Terraform is achievable with the right approach. For small environments, Terraform's native import with generated config works well. For larger or more complex environments, specialized tools that handle discovery, code generation, and state management together can save significant time.
The key is recognizing that generated code is a starting point, not the final product. Plan to spend time refactoring and organizing the output into something maintainable.
Terraback
If you're looking for a tool that handles this workflow automatically, I built Terraback specifically for this problem. It scans your AWS, Azure, or GCP accounts, generates clean Terraform code with proper references and dependencies, and creates valid state files.
Learn More at Terraback.io