Quick Answer
# Terraform auto-detects the deletion — just plan and apply
terraform plan # Shows: "read during apply" or "will be created"
terraform apply # Recreates the missing resource
The Error
Error: reading EC2 Instance (i-0abc123def456): couldn't find resource
Error: error reading S3 Bucket (my-bucket): NoSuchBucket: The specified bucket does not exist
Warning: Value for undeclared variable — The resource aws_instance.web no longer exists
Or during refresh:
aws_instance.web: Refreshing state... [id=i-0abc123def456]
aws_instance.web: Resource not found, removing from state
What Causes This
Someone (or something) deleted the resource outside of Terraform:
- Deleted via AWS Console manually
- Deleted by another automation tool (CloudFormation, Ansible, scripts)
- Auto-scaling terminated the instance
- AWS terminated a spot instance
- Account cleanup or organization policies
Solution 1: Let Terraform Recreate It
# Terraform detects the deletion during refresh
terraform plan
# aws_instance.web has been deleted
- resource "aws_instance" "web" {
- id = "i-0abc123def456"
...
}
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ id = (known after apply)
...
}
terraform apply # Recreates the resource
This is the cleanest fix — Terraform handles it automatically.
Solution 2: Remove from State (Don’t Recreate)
If you don’t want the resource anymore:
# Remove the resource from Terraform state
terraform state rm aws_instance.web
# Now remove the resource block from your .tf files
# Then plan to verify
terraform plan
Solution 3: Import a Replacement
If someone recreated the resource with a different ID:
# Import the new resource
terraform import aws_instance.web i-0newid789
# Or use import block (Terraform 1.5+)
import {
to = aws_instance.web
id = "i-0newid789"
}
Solution 4: Refresh-Only Plan
Detect all drift without making changes:
terraform plan -refresh-only
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform:
# aws_instance.web has been deleted
- resource "aws_instance" "web" { ... }
# aws_security_group.web has been changed
~ resource "aws_security_group" "web" {
~ ingress = [
+ { from_port = 3389, ... } # Someone added RDP access
]
}
Apply the refresh to update state without changing infrastructure:
terraform apply -refresh-only
Prevention: Protect Critical Resources
Terraform Lifecycle
resource "aws_db_instance" "main" {
# ...
lifecycle {
prevent_destroy = true
}
}
This prevents terraform destroy from deleting it, but doesn’t protect against console deletion.
AWS Termination Protection
resource "aws_instance" "critical" {
instance_type = "t3.large"
disable_api_termination = true # Can't delete via API/console
tags = {
Name = "critical-service"
}
}
resource "aws_db_instance" "main" {
deletion_protection = true # RDS deletion protection
}
resource "aws_s3_bucket" "important" {
# Use object lock or bucket policy to prevent deletion
}
Drift Detection in CI/CD
# Nightly drift detection
drift-check:
stage: monitor
script:
- terraform init
- terraform plan -refresh-only -detailed-exitcode
# Exit code 2 = drift detected
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
allow_failure: true
-detailed-exitcode returns:
0— no changes1— error2— changes detected (drift)
Hands-On Courses
- Terraform for Beginners on CopyPasteLearn
- Terraform By Example — practical code examples
Conclusion
When resources are deleted outside Terraform, just run terraform plan — it detects the deletion and offers to recreate. Use terraform state rm if you don’t want it back, or terraform import if it was replaced. Prevent drift with disable_api_termination, deletion_protection, and nightly terraform plan -refresh-only in CI.
