What's Actually Happening
You run terraform destroy to clean up infrastructure, but the operation hangs indefinitely without completing. Resources aren being deleted but Terraform keeps waiting for confirmation that deletion is complete. This blocks cleanup and can leave resources in an inconsistent state.
The Error You'll See
``` module.eks.aws_eks_cluster.main: Still destroying... [10m30s elapsed] module.eks.aws_eks_cluster.main: Still destroying... [11m0s elapsed] module.eks.aws_eks_cluster.main: Still destroying... [12m0s elapsed] ... (continues without progress)
Error: waiting for EKS Cluster (my-cluster) deletion: timeout while waiting for state to become 'deleted' (last state: 'deleting', timeout: 30m0s)
Error: context deadline exceeded
Error: Instance (i-0123456789abcdef0) is in 'shutting-down' state but not transitioning to terminated ```
Why This Happens
Destroy operations hang due to:
- 1.Slow resource deletion - Some resources (EKS, RDS) take 30+ minutes to delete
- 2.Default timeout too short - Destroy timeout insufficient for resource type
- 3.Resource dependencies - Dependent resources blocking deletion
- 4.Protection mechanisms - Resource has deletion protection enabled
- 5.External locks - State locked during destroy
- 6.API issues - Provider not detecting deletion completion
- 7.Stuck in intermediate state - Resource in deleting/shutting-down state
- 8.Final snapshots/backups - Resource creating backup before deletion
Step 1: Check Progress Status
Monitor what's actually happening:
```bash # Check resource status via provider API aws eks describe-cluster --name my-cluster --query 'cluster.status'
# For EC2 instances aws ec2 describe-instances --instance-ids i-0123456789abcdef0 --query 'Reservations[].Instances[].State.Name'
# For RDS aws rds describe-db-instances --db-instance-identifier my-db --query 'DBInstances[].DBInstanceStatus'
# For CloudFront aws cloudfront get-distribution --id E12345678 --query 'Distribution.Status'
# For S3 buckets aws s3 ls my-bucket ```
Step 2: Increase Destroy Timeout
Add explicit destroy timeouts:
```hcl # EKS cluster - slow deletion resource "aws_eks_cluster" "main" { name = "my-cluster"
timeouts { create = "30m" update = "60m" delete = "60m" # EKS can take 30-45 minutes to delete } }
# RDS database - very slow deletion with snapshot resource "aws_db_instance" "main" { allocated_storage = 20 engine = "postgres"
timeouts { delete = "2h" # RDS with final snapshot takes very long }
# Skip final snapshot for faster deletion skip_final_snapshot = true delete_automated_backups = true }
# EC2 instances - can hang on shutdown resource "aws_instance" "main" { timeouts { delete = "30m" } }
# S3 bucket with many objects resource "aws_s3_bucket" "data" { timeouts { delete = "1h" # Depends on number of objects } } ```
Step 3: Handle Dependency Blocking
Resources with dependencies may block:
```bash # Check what depends on the stuck resource terraform state show aws_vpc.main | grep depends_on
# Destroy in reverse order # Target leaf resources first terraform destroy -target=aws_instance.web terraform destroy -target=aws_subnet.public terraform destroy -target=aws_vpc.main
# Or destroy all except problematic terraform destroy -target=module.app terraform destroy -target=module.networking ```
Step 4: Handle Deletion Protection
Check for protection mechanisms:
```bash # RDS deletion protection aws rds describe-db-instances --db-instance-identifier my-db --query 'DBInstances[].DeletionProtection'
# EC2 termination protection aws ec2 describe-instance-attribute --instance-id i-0123456789abcdef0 --attribute disableApiTermination
# S3 object lock aws s3api get-object-lock-configuration --bucket my-bucket ```
Disable protection:
```hcl # RDS - disable deletion protection resource "aws_db_instance" "main" { deletion_protection = false }
# EC2 - allow termination resource "aws_instance" "main" { disable_api_termination = false }
# Or update via API before Terraform destroy aws rds modify-db-instance \ --db-instance-identifier my-db \ --no-deletion-protection
aws ec2 modify-instance-attribute \ --instance-id i-0123456789abcdef0 \ --no-disable-api-termination ```
Step 5: Handle S3 Bucket Deletion
S3 buckets with many objects hang:
```bash # Check bucket contents count aws s3 ls s3://my-bucket --recursive | wc -l
# Empty bucket manually before Terraform destroy aws s3 rm s3://my-bucket --recursive
# For versioned buckets, delete all versions aws s3api list-object-versions --bucket my-bucket \ --query 'Versions[].{Key:Key,VersionId:VersionId}' \ --output text | while read key version; do aws s3api delete-object --bucket my-bucket --key $key --version-id $version done
# Then Terraform can delete bucket terraform destroy -target=aws_s3_bucket.my_bucket ```
Step 6: Handle RDS Final Snapshot
RDS creating final snapshot blocks deletion:
```bash # Check if snapshot being created aws rds describe-db-snapshots --db-snapshot-identifier final-snapshot-name
# If stuck, you may need to wait or manually delete # Terraform configuration should skip final snapshot: ```
```hcl resource "aws_db_instance" "main" { skip_final_snapshot = true # No final snapshot final_snapshot_identifier = "" # Not needed if skip_final_snapshot = true
# Or keep snapshot but be patient skip_final_snapshot = false final_snapshot_identifier = "my-db-final-snapshot"
timeouts { delete = "3h" # Allow time for snapshot creation } } ```
Step 7: Handle EKS Cluster Deletion
EKS clusters have complex dependencies:
```bash # Check cluster status aws eks describe-cluster --name my-cluster --query 'cluster.status'
# Check node groups aws eks list-nodegroups --cluster-name my-cluster
# Delete node groups first aws eks delete-nodegroup --cluster-name my-cluster --nodegroup-name main
# Wait for node groups aws eks wait nodegroup-deleted --cluster-name my-cluster --nodegroup-name main
# Then Terraform can delete cluster terraform destroy -target=aws_eks_cluster.main ```
Configuration for proper ordering:
```hcl resource "aws_eks_cluster" "main" { name = "my-cluster"
timeouts { delete = "45m" } }
resource "aws_eks_node_group" "workers" { cluster_name = aws_eks_cluster.main.name
depends_on = [aws_eks_cluster.main]
timeouts { delete = "30m" } }
# Terraform should destroy node groups before cluster # due to depends_on ```
Step 8: Force Delete Stuck Resources
When resource deletion truly stuck:
```bash # EC2 - force terminate aws ec2 terminate-instances --instance-ids i-0123456789abcdef0
# Wait and check aws ec2 wait instance-terminated --instance-ids i-0123456789abcdef0
# Remove from Terraform state terraform state rm aws_instance.stuck
# Continue destroy terraform destroy ```
For RDS:
```bash # If RDS stuck in deleting # Check for active processes aws rds describe-db-instances --db-instance-identifier my-db
# May need to skip final snapshot and force aws rds delete-db-instance \ --db-instance-identifier my-db \ --skip-final-snapshot \ --delete-automated-backups
# Remove from Terraform state once actually deleted terraform state rm aws_db_instance.stuck ```
Step 9: Use -target for Selective Destroy
Break down destroy into parts:
```bash # Destroy specific resources first terraform destroy -target=aws_instance.web terraform destroy -target=aws_instance.app terraform destroy -target=module.compute
# Then remaining terraform destroy -target=module.networking
# Finally everything else terraform destroy ```
Destroy with exclusions:
```bash # Destroy all except specific resources # Remove from state what you want to keep terraform state rm aws_vpc.keep_this
# Then destroy everything else terraform destroy
# Resource still exists, re-import later terraform import aws_vpc.keep_this vpc-12345678 ```
Step 10: Handle Resource in Wrong State
Resources stuck in transitional states:
```bash # EC2 stuck in shutting-down aws ec2 describe-instances --instance-ids i-stuck --query 'Reservations[].Instances[].State.Name'
# May be process issue - check system log aws ec2 get-console-output --instance-id i-stuck --output text
# Force termination if truly stuck aws ec2 terminate-instances --instance-ids i-stuck
# Wait longer sleep 120 aws ec2 describe-instances --instance-ids i-stuck ```
Step 11: Handle Destroy Errors
Common destroy error patterns:
"Dependency violation":
``bash
# Something depends on resource
# E.g., subnet has instances
# Destroy instances first
terraform destroy -target=aws_instance.web
terraform destroy -target=aws_subnet.public
"Resource in use": ```bash # Resource being used by another service # E.g., AMI used by other instances # Check usage aws ec2 describe-instances --filters "Name=image-id,Values=ami-12345678"
# Remove usage first terraform destroy -target=aws_instance.using_ami ```
"Cannot delete default VPC":
``bash
# Default resources can't be deleted by Terraform
# Remove from state, they'll persist
terraform state rm aws_vpc.default
Verify the Fix
After resolving destroy issues:
```bash # Verify destroy completes terraform destroy
# Should see: # Destroy complete! Resources: 10 destroyed.
# Check state is empty terraform state list
# Verify resources actually deleted aws ec2 describe-instances --query 'Reservations[].Instances[].InstanceId' aws rds describe-db-instances --query 'DBInstances[].DBInstanceIdentifier' ```
Prevention Best Practices
Plan destroy operations:
## Destroy Planning Checklist
1. Check timeouts for slow resources
2. Disable deletion protection
3. Empty S3 buckets first
4. Skip RDS final snapshots
5. Destroy in dependency order
6. Allow sufficient time (2-3 hours for complex setups)Configure resources for easier deletion:
```hcl # Always set timeouts timeouts { delete = "30m" }
# Disable protection by default deletion_protection = false
# Skip final snapshots skip_final_snapshot = true
# Empty S3 buckets before destroy # Use lifecycle rule to abort incomplete multipart uploads lifecycle_rule { enabled = true abort_incomplete_multipart_upload_days = 1 } ```