AWS
S3 Bucket
Terraform
Cloud Infrastructure
Resource Management

How to delete non empty s3 bucket with terraform?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Terraform will not normally delete an S3 bucket if it still contains objects, because S3 itself requires the bucket to be empty first. The standard Terraform solution is force_destroy = true on the bucket resource. That tells the AWS provider to remove the bucket contents as part of destroy, but it should be used carefully because it turns a normally protective failure into a destructive action.

The basic Terraform setting

For an S3 bucket managed by the AWS provider, the usual pattern looks like this:

hcl
1resource "aws_s3_bucket" "example" {
2  bucket        = "my-example-bucket-123456"
3  force_destroy = true
4}

With force_destroy = true, Terraform is allowed to empty the bucket before removing it. Without that setting, terraform destroy typically fails if objects are still present.

This is the cleanest answer because the behavior stays inside the provider instead of relying on shell provisioners or manual AWS CLI cleanup.

Why it matters in real environments

S3 buckets are often long-lived and may contain:

  • uploaded application files
  • logs
  • generated reports
  • versioned objects and delete markers

That means "destroy the infrastructure" can unexpectedly become "erase live data." So force_destroy is appropriate only when bucket contents are disposable or the deletion has been explicitly approved.

Example with versioning awareness

A common pattern is to enable versioning but still allow destruction in non-production environments:

hcl
1resource "aws_s3_bucket" "artifacts" {
2  bucket        = "my-dev-artifacts-123456"
3  force_destroy = true
4}
5
6resource "aws_s3_bucket_versioning" "artifacts" {
7  bucket = aws_s3_bucket.artifacts.id
8
9  versioning_configuration {
10    status = "Enabled"
11  }
12}

The important operational point is that versioning can make a bucket look empty in the console while old versions or delete markers still exist. The provider has to handle those objects too during destroy, which is one reason large bucket cleanup can take time.

What not to do by default

You will sometimes see examples that attach a local-exec provisioner and call aws s3 rm --recursive during destroy. That can work, but it is usually the wrong default because:

  • it depends on the AWS CLI being installed
  • it mixes imperative cleanup into declarative infrastructure
  • it is harder to reason about and test than provider-native behavior

If the provider supports the behavior directly, prefer the provider setting.

Extra blockers you still need to think about

Even with force_destroy = true, bucket deletion may still be blocked by surrounding S3 features or policy choices, for example:

  • restrictive bucket policies
  • object lock retention or legal holds
  • insufficient IAM permissions
  • replication or lifecycle behavior still in flight

So force_destroy is "allow Terraform to empty the bucket," not "guarantee deletion no matter what."

Use it differently in dev and prod

In development or ephemeral test stacks, force_destroy = true is often reasonable. In production, many teams intentionally leave it disabled so that a destroy fails loudly instead of silently deleting important objects.

That can be expressed as an environment-specific variable:

hcl
1variable "allow_bucket_force_destroy" {
2  type    = bool
3  default = false
4}
5
6resource "aws_s3_bucket" "example" {
7  bucket        = "my-example-bucket-123456"
8  force_destroy = var.allow_bucket_force_destroy
9}

This makes the destructive behavior opt-in instead of accidental.

Common Pitfalls

The biggest mistake is enabling force_destroy on a bucket that contains valuable production data. Terraform will treat that data as part of the resource lifecycle.

Another issue is assuming the bucket is empty just because no current objects are visible. Versioned buckets may still contain older versions or delete markers.

People also use shell provisioners for cleanup when the provider setting is simpler and safer to maintain.

Finally, bucket deletion can still fail if IAM permissions, object-lock settings, or policies prevent object removal. force_destroy does not override S3 security controls.

Summary

  • The normal Terraform answer is force_destroy = true on aws_s3_bucket.
  • That allows the provider to remove bucket contents before deleting the bucket.
  • It is appropriate for disposable or explicitly approved data, especially in dev environments.
  • It should be used cautiously for versioned or production buckets.
  • If deletion still fails, check permissions, object lock, policies, and versioned contents.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.