This error occurs when Terraform tries to delete an S3 bucket that still contains objects. By default, Terraform prevents bucket deletion to protect against accidental data loss. The fix is to enable force_destroy in your aws_s3_bucket resource or manually delete objects first.
AWS S3 requires that buckets be completely empty before they can be deleted. When you run `terraform destroy` on an S3 bucket resource, Terraform calls the AWS API to delete the bucket. If the bucket contains any objects (files), AWS returns the "BucketNotEmpty" error. This is a safety feature - AWS doesn't allow deletion of non-empty buckets to prevent accidental data loss. Terraform honors this AWS requirement by default. The error typically occurs when: - You created an S3 bucket with Terraform, uploaded objects to it (either manually or through other infrastructure), then tried to destroy your Terraform stack - The bucket has versioning enabled and contains object versions or delete markers - The bucket was populated by application logs, backups, or other automated processes The solution depends on your use case: do you want to delete the objects along with the bucket, or preserve the bucket and its contents?
The quickest fix is to set force_destroy = true on your aws_s3_bucket resource. This tells Terraform to delete all objects in the bucket before deleting the bucket itself.
resource "aws_s3_bucket" "example" {
bucket = "my-terraform-bucket"
force_destroy = true
}If you have a bucket without force_destroy already defined:
# Before (fails on destroy)
resource "aws_s3_bucket" "logs" {
bucket = "my-app-logs"
}
# After (allows destroy)
resource "aws_s3_bucket" "logs" {
bucket = "my-app-logs"
force_destroy = true
}Then apply the change:
terraform applyNow terraform destroy will work, removing all objects and then the bucket.
Warning: This is destructive. Only use this if you're comfortable with the bucket and all its contents being permanently deleted.
After adding force_destroy = true, you must apply the change to the bucket before destroying:
# Step 1: Apply the force_destroy change
terraform apply
# Step 2: Destroy the bucket (now with force_destroy enabled)
terraform destroyYou cannot skip the apply step. If you try to destroy immediately without applying the force_destroy change, the error will still occur.
If you're using terraform destroy with auto-approve:
terraform apply -auto-approve
terraform destroy -auto-approveIf you have a large Terraform configuration and want to update only the S3 bucket:
# Apply changes only to the S3 bucket resource
terraform apply -target=aws_s3_bucket.example
# Then destroy
terraform destroyOr if the bucket is in a module:
terraform apply -target=module.storage.aws_s3_bucket.example
terraform destroyThis approach is useful when you don't want to apply other infrastructure changes while fixing the bucket destruction issue.
If you don't want to enable force_destroy in your code, you can manually delete the bucket contents first:
1. Go to the AWS S3 console: https://s3.console.aws.amazon.com/s3/
2. Find your bucket and click on it
3. Click "Select All" to select all objects
4. Click "Delete" and confirm
Or use the AWS CLI:
# Remove all objects from the bucket
aws s3 rm s3://my-terraform-bucket --recursive
# If versioning is enabled, you may need to remove delete markers too
aws s3api list-object-versions --bucket my-terraform-bucket --output json \
| jq -r '.DeleteMarkers[] | "\(.Key) \(.VersionId)"' \
| while read key version_id; do
aws s3api delete-object --bucket my-terraform-bucket --key "$key" --version-id "$version_id"
done
# Now terraform destroy should work
terraform destroyAfter the bucket is empty, terraform destroy will succeed without needing to modify your code.
Drawback: You have to do this manually each time, and it's error-prone in CI/CD pipelines.
If your bucket has versioning enabled, simply deleting current versions isn't enough. You must also remove delete markers:
# List all versions and delete markers
aws s3api list-object-versions --bucket my-terraform-bucket --output table
# Remove all object versions
aws s3api list-object-versions --bucket my-terraform-bucket \
--query 'Versions[].{Key:Key,VersionId:VersionId}' \
--output text | while read key version_id; do
aws s3api delete-object --bucket my-terraform-bucket --key "$key" --version-id "$version_id"
done
# Remove all delete markers
aws s3api list-object-versions --bucket my-terraform-bucket \
--query 'DeleteMarkers[].{Key:Key,VersionId:VersionId}' \
--output text | while read key version_id; do
aws s3api delete-object --bucket my-terraform-bucket --key "$key" --version-id "$version_id"
doneIf using force_destroy = true, Terraform handles this automatically.
If you want to manage an existing S3 bucket with Terraform but keep it safe from accidental destruction:
# Define the resource WITHOUT force_destroy
resource "aws_s3_bucket" "existing" {
bucket = "my-existing-bucket"
force_destroy = false # Explicit - prevents accidents
}Import the existing bucket:
terraform import aws_s3_bucket.existing my-existing-bucketNow terraform destroy will fail safely with BucketNotEmpty, protecting the bucket from accidental deletion. This is useful for:
- Shared infrastructure
- Buckets with important data
- Buckets shared across multiple Terraform projects
### Force Destroy Behavior in Terraform AWS Provider
When you set force_destroy = true, Terraform performs this sequence:
1. Lists all objects in the bucket
2. Deletes every object version (if versioning is enabled)
3. Deletes every delete marker
4. Finally deletes the bucket
This is recursive and handles nested prefixes correctly.
### Versioning Gotchas
If a bucket has versioning enabled and you delete objects without deleting versions:
- Current object versions are deleted (versioning = SUSPENDED)
- But all previous versions and delete markers remain
- The bucket is still not empty
Terraform's force_destroy removes all versions and delete markers, so it handles this correctly. The AWS Console's "Empty" button also handles this.
### S3 Bucket Policies and Permissions
Ensure your IAM user/role has the required permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:ListBucket",
"s3:ListBucketVersions",
"s3:DeleteBucket"
],
"Resource": [
"arn:aws:s3:::my-terraform-bucket",
"arn:aws:s3:::my-terraform-bucket/*"
]
}
]
}### Using force_destroy in Production
For production workloads, consider:
1. Separate state files: Use different Terraform workspaces or modules for persistent vs. ephemeral resources
2. Prevent destroy: Use Terraform's prevent_destroy = true to protect critical buckets:
resource "aws_s3_bucket" "production_data" {
bucket = "prod-data"
force_destroy = false
lifecycle {
prevent_destroy = true
}
}3. Require approval: Use terraform destroy with manual review rather than automated destruction
4. Backup strategy: Always maintain backups outside the S3 bucket
### Bucket Ownership Control
AWS introduced Bucket Ownership Controls. If your bucket has strict ownership restrictions, you may need to adjust those before destruction:
resource "aws_s3_bucket_ownership_controls" "example" {
bucket = aws_s3_bucket.example.id
rule {
object_ownership = "BucketOwnerPreferred"
}
}### S3 Access Logging Buckets
If your bucket is used as a target for S3 access logging:
# Logging bucket - safe to destroy
resource "aws_s3_bucket" "logs" {
bucket = "my-logs-bucket"
force_destroy = true
}
# Source bucket - references the logging bucket
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
}
resource "aws_s3_bucket_logging" "data" {
bucket = aws_s3_bucket.data.id
target_bucket = aws_s3_bucket.logs.id
target_prefix = "log/"
}When destroying, Terraform will first remove the logging configuration from the source bucket, then destroy the logging bucket. Order matters in your destroy operation.
Error: Error rendering template: template not found
How to fix "template not found" error in Terraform
Error: Error generating private key
How to fix 'Error generating private key' in Terraform
Error creating Kubernetes Service: field is immutable
How to fix "field is immutable" errors in Terraform
Error: Error creating local file: open: permission denied
How to fix "Error creating local file: permission denied" in Terraform
Error: line endings have changed from CRLF to LF
Line endings have changed from CRLF to LF in Terraform