Terraform Bulk Import: Bring Existing AWS Resources Under Management
Import dozens of existing AWS resources into Terraform at once using import blocks, for_each, and generate-config-out.
DevOps
Learn Terraform Stacks for coordinated multi-component infrastructure deployments. Understand components, deferred changes
Terraform Stacks became generally available in late 2025 and represent the biggest new Terraform feature since workspaces. Stacks let you deploy multiple Terraform configurations as a single, coordinated unit — solving the "how do I manage networking + compute + database together?" problem that teams have worked around for years.
Without Stacks, deploying a full environment requires multiple terraform apply commands in the right order:
# Manual orchestration — error-prone
cd networking/
terraform apply # 1. Create VPC first
cd ../database/
terraform apply # 2. Create RDS (needs VPC)
cd ../compute/
terraform apply # 3. Create ECS (needs VPC + DB endpoint)
cd ../monitoring/
terraform apply # 4. Create dashboards (needs all of the above)Problems:
A Stack defines components (individual Terraform configurations) and their dependencies:
my-stack/
├── stack.tfstack.hcl # Stack definition
├── deployments.tfdeploy.hcl # Deploy targets (dev, prod)
├── components/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── database/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── compute/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf# stack.tfstack.hcl
variable "region" {
type = string
}
variable "environment" {
type = string
}
# Component 1: Networking
component "networking" {
source = "./components/networking"
inputs = {
region = var.region
environment = var.environment
vpc_cidr = "10.0.0.0/16"
}
}
# Component 2: Database (depends on networking)
component "database" {
source = "./components/database"
inputs = {
region = var.region
environment = var.environment
vpc_id = component.networking.vpc_id # ← Dependency
subnet_ids = component.networking.private_subnet_ids
}
}
# Component 3: Compute (depends on networking + database)
component "compute" {
source = "./components/compute"
inputs = {
region = var.region
environment = var.environment
vpc_id = component.networking.vpc_id
subnet_ids = component.networking.private_subnet_ids
db_endpoint = component.database.endpoint # ← Dependency
db_secret_arn = component.database.secret_arn
}
}# deployments.tfdeploy.hcl
deployment "development" {
inputs = {
region = "us-east-1"
environment = "dev"
}
}
deployment "staging" {
inputs = {
region = "us-east-1"
environment = "staging"
}
}
deployment "production" {
inputs = {
region = "us-east-1"
environment = "prod"
}
}Each component is a regular Terraform configuration:
# components/networking/main.tf
variable "region" { type = string }
variable "environment" { type = string }
variable "vpc_cidr" { type = string }
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = { Name = "${var.environment}-vpc" }
}
resource "aws_subnet" "private" {
count = 3
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
tags = { Name = "${var.environment}-private-${count.index}" }
}
output "vpc_id" { value = aws_vpc.main.id }
output "private_subnet_ids" { value = aws_subnet.private[*].id }| Feature | Workspaces | Stacks |
|---|---|---|
| Purpose | Same config, different state | Multiple configs, coordinated lifecycle |
| Components | Single root module | Multiple root modules |
| Dependencies | Manual (remote state data sources) | Declared (component.X.output) |
| Deployment order | Manual | Automatic (dependency graph) |
| Multi-env | One workspace per env | Deployment targets per env |
| Destroy order | Manual (reverse) | Automatic (reverse dependency) |
| Requires HCP | No (CLI or HCP) | Yes (HCP Terraform only) |
| OpenTofu support | ✅ Yes | ❌ No |
# Same config, different environments
terraform workspace select dev
terraform apply
terraform workspace select prod
terraform applyBest for: isolated environments using the same Terraform code.
Best for: multi-component systems where networking, database, compute, and monitoring need coordinated deployment and destruction.
Stacks support deferred changes — when one component can't be fully planned because it depends on a not-yet-created resource:
Component "networking" → Plan: +5 resources
Component "database" → Plan: +3 resources (deferred: subnet_ids unknown until networking applies)
Component "compute" → Plan: +8 resources (deferred: db_endpoint unknown until database applies)Stacks automatically handle this: apply networking first, then plan database with real values, then apply database, then plan and apply compute.
# Create stack structure
mkdir -p my-stack/components/{networking,database,compute}
# Create stack definition
cat > my-stack/stack.tfstack.hcl << 'EOF'
component "networking" {
source = "./components/networking"
inputs = { region = var.region }
}
EOF
# Create deployment targets
cat > my-stack/deployments.tfdeploy.hcl << 'EOF'
deployment "dev" {
inputs = { region = "us-east-1" }
}
EOF
# Push to HCP Terraform
# Configure stack in HCP UI or APITerraform Stacks solve the multi-component orchestration problem: declare components with explicit dependencies, and Stacks handles the apply/destroy order automatically. The tradeoff is that Stacks require HCP Terraform — they're not available in the open-source CLI or OpenTofu. For teams already on HCP managing complex environments (networking + database + compute + monitoring), Stacks eliminate the glue scripts and manual ordering that made multi-component deployments fragile.
Import dozens of existing AWS resources into Terraform at once using import blocks, for_each, and generate-config-out.
Learn Terraform data sources to read existing AWS resources, look up AMIs, query remote state, and reference external information in your configurations.
Learn when to use Terraform depends_on for explicit resource dependencies. Understand implicit vs explicit dependencies, common use cases
Terraform for_each vs count explained with practical examples. Learn when to use each, how to migrate from count to for_each