Terraform S3 Bucket Management: Modules, Resources, and Modern AWS Patterns

Creating and managing Amazon S3 buckets with Terraform has evolved from simple resource declarations to comprehensive modules that handle versioning, encryption, replication, logging, and access control. The S3 service provides Simple Storage Service for data lakes, websites, mobile applications, backups, archives, and enterprise applications with comprehensive access management for granular permissions. Terraform enables declarative configuration of buckets, objects, public access blocks, and ownership controls.

Core S3 Bucket Resources in Modern Terraform

The Terraform AWS provider separates bucket creation from configuration to align with current AWS best practices. A minimal bucket definition starts with the provider configuration and a single resource.

```hcl
provider "aws" {
region = "us-east-1"
}

resource "awss3bucket" "example" {
bucket = "my-unique-bucket-name-12345"
}
```

After writing provider.tf and main.tf, the standard workflow is:

  • $ terraform init
    This is the first command to run. It initializes the working directory and downloads the AWS provider.
  • $ terraform plan
    The second command shows the bucket, public access block, and ownership controls that Terraform is about to create.
  • $ terraform apply
    Apply the Terraform configuration using the terraform apply command, which will eventually create an S3 bucket in AWS.

In modern Terraform, keep the bucket definition minimal and manage settings like versioning or encryption with separate resources when needed. The main resources used in current guides are:

  • awss3bucket
  • awss3object
  • awss3bucketpublicaccess_block
  • awss3bucketownershipcontrols

These resources let you create a bucket, upload objects, block public access, and manage bucket ownership behavior.

Uploading and Managing Objects

Once the bucket exists, objects can be managed with Terraform.

hcl resource "aws_s3_bucket_object" "example" { bucket = aws_s3_bucket.example.id key = "path/to/object" source = "local/path/to/file" }

If you are working from older examples, you may still see awss3bucketobject, but that resource has been deprecated in favor of awss3_object. This approach works well when you want Terraform to manage a small number of supporting files along with the infrastructure itself. For very large numbers of files or frequent bulk uploads, use a data transfer or deployment tool rather than Terraform.

Public Access, Ownership, and ACL Evolution

Older Terraform examples often set acl = "private" on the bucket and then discuss ACLs as the main way to manage access. That is no longer the best default for new buckets.

The S3 bucket will allow public access by default, which we don’t want in this case. We want it to be private.

Modern guidance recommends blocking public access explicitly and using bucket ownership controls. In the updated configuration, Terraform may also show additional S3 resources such as public access blocking and ownership controls.

Key changes in the modern model:

  • Block public access by default using awss3bucketpublicaccess_block
  • Enforce bucket ownership with awss3bucketownershipcontrols
  • Avoid relying on ACLs; BucketOwnerEnforced disables ACLs automatically

Note: This apply screenshot is illustrative and may not exactly match the current code sample. The latest version of this guide uses a more modern S3 configuration, so the number of resources created may be different.

Common pitfalls with simple implementations:

  • The S3 bucket will allow public access by default, which we don’t want in this case. We want it to be private.
  • The S3 bucket can’t be deleted by terraform if it contains any files. So running terraform destroy won’t work.

For test environments where create and destroy is required, force_destroy can be set on the module to allow deletion of non-empty buckets.

Terraform Modules for S3 Buckets

Modules encapsulate best practices and reduce boilerplate. Two prominent community modules address S3 bucket creation.

terraform-aws-modules/terraform-aws-s3-bucket

This Terraform module creates S3 bucket on AWS with all or almost all features provided by Terraform AWS provider. Supported features include:

  • static web-site hosting
  • access logging
  • versioning
  • CORS
  • lifecycle rules
  • server-side encryption
  • object locking
  • Cross-Region Replication (CRR)
  • ELB log delivery bucket policy
  • ALB/NLB log delivery bucket policy
  • WAF log delivery bucket policy
  • Account-level Public Access Block
  • S3 Directory Bucket
  • S3 Table Bucket
  • S3 Vectors

Example usage for a private bucket with versioning:

hcl module "s3_bucket" { source = "terraform-aws-modules/s3-bucket/aws" bucket = "my-s3-bucket" acl = "private" control_object_ownership = true object_ownership = "ObjectWriter" versioning = { enabled = true } }

Example for a log delivery bucket:

hcl module "s3_bucket_for_logs" { source = "terraform-aws-modules/s3-bucket/aws" bucket = "my-s3-bucket-for-logs" acl = "log-delivery-write" force_destroy = true control_object_ownership = true object_ownership = "ObjectWriter" attach_elb_log_delivery_policy = true }

With ALB/NLB log delivery:

hcl module "s3_bucket_for_logs" { source = "terraform-aws-modules/s3-bucket/aws" bucket = "my-s3-bucket-for-logs" force_destroy = true control_object_ownership = true object_ownership = "ObjectWriter" attach_elb_log_delivery_policy = true attach_lb_log_delivery_policy = true }

The module also supports WAF log delivery buckets with similar configuration patterns.

cloudposse/terraform-aws-s3-bucket

This module creates an S3 bucket with support for versioning, lifecycles, object locks, replication, encryption, ACL, bucket object policies, and static website hosting.

For backward compatibility, it sets the S3 bucket ACL to private and the s3objectownership to ObjectWriter. Moving forward, setting s3objectownership to BucketOwnerEnforced is recommended, and doing so automatically disables the ACL.

This module blocks public access to the bucket by default. See blockpublicacls, blockpublicpolicy, ignorepublicacls, and restrictpublicbuckets to change the settings. See AWS documentation for more details.

This module can optionally create an IAM User with access to the S3 bucket. This is inherently insecure in that to enable anyone to become the User, access keys must be generated, and anything generated by Terraform is stored unencrypted in the Terraform state.

S3 Bucket Configuration Options Comparison

Feature terraform-aws-modules cloudposse Native Resources
Versioning Yes Yes awss3bucket_versioning
Lifecycle Rules Yes Yes awss3bucketlifecycleconfiguration
Object Lock Yes Yes awss3bucketobjectlock_configuration
Server-Side Encryption Yes Yes awss3bucketserversideencryptionconfiguration
Cross-Region Replication Yes Yes awss3bucketreplicationconfiguration
Static Website Hosting Yes Yes website configuration block
Public Access Block Yes, Account-level Default blocked awss3bucketpublicaccess_block
ELB/ALB/NLB Log Delivery Policies Yes Variable Bucket policy
WAF Log Delivery Yes Variable Bucket policy
S3 Directory Bucket Yes No awss3directory_bucket
S3 Table Bucket Yes No awss3table_bucket
S3 Vectors Yes No aws_s3vectors

IAM Integration and Private Access Patterns

In practice, S3 buckets are often paired with EC2 instances or other workloads using IAM for access control. A common pattern is to set up an S3 bucket so it can only be accessed privately and the EC2 instance will get access to the S3 bucket using IAM.

The bucket creation remains simple:

hcl resource "aws_s3_bucket" "some-bucket" { bucket = "my-bucket-name" }

Private access is enforced by blocking public access and using IAM roles attached to the EC2 instance. The S3 bucket needs to be private so we can only access it from the EC2 instance. Assuming a test environment, force_destroy can be enabled to allow create and destroy cycles during testing. In production, deletion of the S3 bucket would never be desired.

Step-by-Step Setup from Scratch

For new users, the typical local setup follows these steps.

Prerequisite:
Step 1: Open the cmd if you are in Windows and configure aws using the aws configure command.
aws configure

Step 2: After that open a code editor and make a file called provider.tf
.tf is the file extension for the terraform file. This file will store the information about the cloud provider, version, and region for the s3 bucket.

```hcl
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.33.0"
}
}
}

provider "aws" {
region = "us-east-1"
}
```

After this open terminal where this file is located and type terraform init to initialize terraform. The Terraform init command prepares the working directory for use with Terraform. It initialises the backend, any child module installation and any plugin installation.

Step 3: Now we have defined our provider. Let’s create S3 bucket, make a file called main.tf in the same directory where provider.tf file is located. In this file we have define the resource, bucket name. The bucket name should be unique, here the bucket name is "my-s3-test-bucket02"

hcl resource "aws_s3_bucket" "my_bucket" { bucket = "my-s3-test-bucket02" }

Run terraform init, terraform plan, and terraform apply to create the bucket. After the apply completes, you should see a new S3 bucket named spacelift-test1-s3 in your AWS account, depending on the name used in the configuration.

Best Practices and Operational Notes

S3 stands for Simple Storage Service. S3 buckets are cloud storage services by Amazon Web Service. It is used to store objects, It consists of data in any format like documents, images, videos, and application code. These are highly scalable.

When using Terraform:

  • Keep bucket names globally unique.
  • Prefer bucket ownership controls over ACLs for new buckets.
  • Use modules for log delivery buckets to avoid manual policy errors.
  • Set force_destroy = true only in non-production environments.
  • Separate object management from infrastructure provisioning for large datasets.
  • Use awss3bucketpublicaccess_block to enforce deny by default.
  • Review versioning, encryption, and lifecycle rules before production deployment.

Modern Terraform configurations favor minimal resource definitions with separate resources for versioning, encryption, and public access. This pattern improves readability and matches AWS recommended configurations.

Conclusion

Terraform S3 bucket management spans from minimal resource declarations to feature-rich modules that cover versioning, encryption, replication, logging, and public access controls. Modern AWS provider patterns encourage separating bucket creation from configuration, using awss3bucketpublicaccessblock and awss3bucketownership_controls instead of ACL defaults, and selecting modules that encapsulate best practices for static website hosting, log delivery, and cross-region replication.

The terraform-aws-modules/s3-bucket module provides comprehensive support for static web-site hosting, access logging, versioning, CORS, lifecycle rules, server-side encryption, object locking, CRR, ELB/ALB/NLB/WAF log delivery policies, and newer bucket types like S3 Directory Bucket, S3 Table Bucket, and S3 Vectors. The cloudposse alternative offers versioning, lifecycles, object locks, replication, encryption, ACL, bucket object policies, and static website hosting with backward compatible ACL settings and default public access blocking.

For teams needing private, IAM-controlled access, pairing an S3 bucket with IAM roles and using ownership controls ensures the bucket remains private by default while allowing controlled access from EC2 or other workloads. For testing, force_destroy and careful naming conventions allow safe create and destroy cycles without risking production data loss.

Sources

  1. github.com/terraform-aws-modules/terraform-aws-s3-bucket
  2. spacelift.io/blog/terraform-aws-s3-bucket
  3. www.sammeechward.com/s3-and-iam-with-terraform
  4. github.com/cloudposse/terraform-aws-s3-bucket
  5. www.geeksforgeeks.org/devops/create-aws-s3-bucket-using-terraform/

Related Posts