TerraformPilot

DevOps

Fix Terraform Error - Error Deleting S3 Bucket - BucketNotEmpty

Fix BucketNotEmpty errors when destroying S3 buckets in Terraform. Handle versioned objects, force_destroy, lifecycle rules, and manual cleanup.

LLuca Berton1 min read

Quick Answer

#

S3 buckets must be empty before deletion. Set force_destroy = true in Terraform to auto-delete all objects, or empty the bucket manually with AWS CLI before destroying.

The Error

#
Error: error deleting S3 Bucket (my-bucket): BucketNotEmpty: 
The bucket you tried to delete is not empty

What Causes This

#
  • Bucket contains objects (files)
  • Bucket has versioned objects (delete markers + old versions)
  • Bucket has incomplete multipart uploads
  • force_destroy is not set (defaults to false)

How to Fix It

#

Solution 1: Set force_destroy = true

#
resource "aws_s3_bucket" "data" {
  bucket        = "my-data-bucket"
  force_destroy = true  # Deletes ALL objects when bucket is destroyed
}

Then:

terraform destroy  # Will empty the bucket automatically

Solution 2: Empty the Bucket Manually

#
# Delete all objects
aws s3 rm s3://my-bucket --recursive
 
# If versioning is enabled, delete all versions too
aws s3api list-object-versions --bucket my-bucket \
  --query 'Versions[].{Key:Key,VersionId:VersionId}' \
  --output json | \
  jq -c '.[]' | while read obj; do
    key=$(echo $obj | jq -r '.Key')
    version=$(echo $obj | jq -r '.VersionId')
    aws s3api delete-object --bucket my-bucket --key "$key" --version-id "$version"
  done
 
# Delete all delete markers
aws s3api list-object-versions --bucket my-bucket \
  --query 'DeleteMarkers[].{Key:Key,VersionId:VersionId}' \
  --output json | \
  jq -c '.[]' | while read obj; do
    key=$(echo $obj | jq -r '.Key')
    version=$(echo $obj | jq -r '.VersionId')
    aws s3api delete-object --bucket my-bucket --key "$key" --version-id "$version"
  done
 
# Now destroy
terraform destroy

Solution 3: Abort Multipart Uploads

#
# List incomplete multipart uploads
aws s3api list-multipart-uploads --bucket my-bucket
 
# Abort them
aws s3api abort-multipart-upload --bucket my-bucket \
  --key "large-file.zip" --upload-id "abc123"

force_destroy Considerations

#
ScenarioUse force_destroy?
Dev/staging buckets✅ Yes — easy cleanup
Production data buckets❌ No — protect against accidental deletion
CI/CD test buckets✅ Yes
Terraform state buckets❌ No — use prevent_destroy instead
Log bucketsDepends — maybe lifecycle rules instead
# Production: protect against accidental deletion
resource "aws_s3_bucket" "production" {
  bucket = "production-data"
 
  lifecycle {
    prevent_destroy = true  # Terraform will refuse to destroy
  }
}

Troubleshooting Checklist

#
  1. ✅ Does the bucket have objects? (aws s3 ls s3://bucket)
  2. ✅ Does it have versioned objects? (aws s3api list-object-versions)
  3. ✅ Are there incomplete multipart uploads?
  4. ✅ Is force_destroy = true set?
#

Conclusion

#

BucketNotEmpty means there are objects (or versioned objects) in the bucket. Set force_destroy = true for non-production buckets, or empty manually with AWS CLI. For production, use prevent_destroy lifecycle rules to avoid accidental deletion entirely.

#Terraform#Troubleshooting#DevOps#Error Fix#Infrastructure as Code

Share this article