Terraform is an open-source tool developed by HashiCorp that allows you to define your cloud infrastructure using simple, human-readable configuration files. Terraform supports multiple cloud providers, including AWS, Azure, Google Cloud, and many others, making it an indispensable tool for DevOps professionals. In today’s cloud-native world, managing infrastructure efficiently is key to scalable and reliable applications. Terraform, an Infrastructure as Code tool, allows you to define your cloud resources declaratively. In this guide, we’ll explore how to create an AWS S3 bucket using Terraform, enabling you to automate your storage needs with ease.
Amazon Simple Storage Service is a highly scalable, durable, and secure object storage service. An Amazon S3 bucket is a storage service offered by AWS for storing data lakes, websites, mobile applications, backups and restores, archives, and enterprise applications. The S3 stands for Simple Storage Service, which can be scaled based on individual or organization needs. In addition to providing storage, Amazon S3 also offers comprehensive access management, which can help you set up very granular permissions. Common use cases include storing backups and archives, hosting static websites, and managing large datasets for big data analytics. With Terraform, you can automate the creation and management of S3 buckets, ensuring consistency across environments.
Prerequisites and Context for Terraform S3 Automation
Before getting started, ensure you have the following:
- An AWS account
- Terraform installed on your local machine
- AWS CLI configured with appropriate credentials
The presence of an AWS account establishes the identity and billing boundary under which the bucket will exist. Terraform installed on the local machine provides the CLI that interprets configuration files and communicates with the AWS provider. AWS CLI configured with appropriate credentials supplies the authentication material Terraform uses when it calls AWS APIs. Together these three items form the execution context that allows declarative code to materialize as real infrastructure.
Determine the purpose of your S3 bucket. Purpose determination shapes downstream decisions about naming, access controls, versioning, lifecycle, and logging. A bucket intended for static web hosting carries different access patterns than a bucket used for backups and archives.
Core Terraform Resources for S3 Bucket Management
This blog post shows how to create and manage an AWS S3 bucket with Terraform using modern AWS provider patterns.
The main Terraform resources used in this guide are:
- awss3bucket
- awss3object
- awss3bucketpublicaccess_block
- awss3bucketownershipcontrols
These resources let you create a bucket, upload objects, block public access, and manage bucket ownership behavior.
| Resource | Role in S3 Management |
|---|---|
| awss3bucket | Creates the bucket itself |
| awss3object | Uploads objects to the bucket |
| awss3bucketpublicaccess_block | Blocks public access |
| awss3bucketownershipcontrols | Manages bucket ownership behavior |
The separation of concerns between bucket creation and object upload reflects modern provider patterns. Keeping the bucket definition minimal and managing settings like versioning or encryption with separate resources when needed reduces coupling and makes plan output more readable.
Provider Configuration and Minimal Bucket Definition
Create the bucket with the awss3bucket resource after configuring the AWS provider. In modern Terraform, keep the bucket definition minimal and manage settings like versioning or encryption with separate resources when needed.
provider "aws" {
region = "us-east-1"
}
resource "aws_s3_bucket" "example" {
bucket = "my-unique-bucket-name-12345"
}
Using the resource block, we've defined a new resource of type awss3bucket. This tells Terraform that we want to create a new S3 bucket in our AWS account. Inside the awss3bucket block, we've specified the name of our bucket using the bucket field. In this case, we've named it terraform-experiments, but you can choose any name that meets the requirements for S3 bucket names.
It’s worth noting that while the bucket field is optional, it's considered a best practice to give your resources descriptive names to help with organization and management.
Initialization and Workflow Commands
Before we can apply our configuration and create the S3 bucket, we need to initialize the Terraform module by running terraform init in our terminal. This command downloads the necessary provider plugins and sets up the backend for storing the state file.
Terraform is not limited to a specific cloud; rather, it uses provider plugins to facilitate infrastructure management across all major cloud providers. Additionally, there are community plugins available for other popular services, such as Cloudflare.
To see a preview of the resource that will be created, you can run the terraform plan command in your terminal. This will show you what changes Terraform will make to your infrastructure based on the configuration in your main.tf file.
The workflow steps are:
- $ terraform init
This is the first command we are going to run. It initializes the working directory and downloads the AWS provider. - $ terraform plan
The second command would be to run a Terraform plan. This 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.
Note: These screenshots reflect an earlier version of the example. The updated code in this article uses current Terraform AWS provider patterns, so resource names and plan/apply output may differ slightly.
After the apply completes, you should see a new S3 bucket named spacelift-test1-s3 in your AWS account.
Bucket Naming and Organizational Best Practices
Inside the awss3bucket block, we've specified the name of our bucket using the bucket field. In this case, we've named it terraform-experiments, but you can choose any name that meets the requirements for S3 bucket names.
It’s worth noting that while the bucket field is optional, it's considered a best practice to give your resources descriptive names to help with organization and management.
Descriptive names reduce operational ambiguity when multiple teams share an AWS account. Names that encode purpose and environment make it easier to identify ownership during audits and cost allocation.
Uploading Objects and Managing Content
In the previous step, we created an S3 bucket with Terraform. In this step, we will upload files to that bucket using the awss3object resource.
If you are working from older examples, you may still see awss3bucket_object, but that resource has been deprecated.
In the updated configuration, Terraform may also show additional S3 resources such as public access blocking and ownership controls.
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.
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.
There are many more things that you can do with Terraform and the S3 Bucket. Here is a guide on how to rename an AWS S3 bucket in Terraform, which can help you rename your S3 bucket.
Public Access Control and Ownership Model
Now that the bucket and objects are in place, the next step is to make sure the bucket follows the modern S3 access model.
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.
Modern configuration favors explicit public access blocking and ownership controls over ACLs. The resources awss3bucketpublicaccessblock and awss3bucketownership_controls provide granular, predictable controls that align with AWS security best practices.
Deletion Behavior and Destroy Workflow
Delete S3 Bucket using Terraform
When you are done working with the bucket, Terraform can remove all of the resources it created by running terraform destroy.
Terraform will delete the managed S3 objects first and then delete the bucket itself. This ordering matters because S3 buckets must be empty before AWS allows them to be removed.
As always, review the destroy plan carefully before confirming the operation, especially if you are working in a shared or production AWS account.
Note: The destroy output shown here was generated from an earlier version of the example. In the updated configuration, Terraform may also remove public access block and ownership control resources.
As you can see in the screenshot, Terraform has deleted the resources in reverse-chronological order, starting with test2.txt, then test2.txt, and finally the bucket spacelift-test1-s3.
Module-Based S3 Bucket Creation
Terraform module which creates S3 bucket on AWS with all or almost all features provided by Terraform AWS provider.
These features of S3 bucket configurations are supported:
- static web-site hosting
- access logging
- versioning
- CORS
- lifecycle rules
- server-side encryption
- object locking
- Cross-Region Replication
- 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
| Feature Category | Examples |
|---|---|
| Hosting and access | static web-site hosting, CORS, access logging |
| Data protection | versioning, server-side encryption, object locking |
| Replication and region | Cross-Region Replication |
| Logging delivery | ELB log delivery bucket policy, ALB/NLB log delivery bucket policy, WAF log delivery bucket policy |
| Security posture | Account-level Public Access Block |
| Advanced bucket types | S3 Directory Bucket, S3 Table Bucket, S3 Vectors |
Module usage examples:
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
}
}
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
}
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 pattern centralizes common S3 configurations and reduces repetition. Setting controlobjectownership = true with objectownership = "ObjectWriter" aligns with the modern ownership model. Enabling versioning = { enabled = true } protects against accidental deletion. Setting forcedestroy = true allows deletion of non-empty buckets during destroy, which is useful for log buckets that accumulate objects.
Logging and Delivery Policies
Module examples for log delivery illustrate how Terraform can wire AWS services to S3.
attachelblogdeliverypolicy = true is required for ALB logs. attachlblogdeliverypolicy = true is required for ALB/NLB logs.
The acl = "log-delivery-write" setting is specifically intended for buckets that receive logs from load balancers. The module exposes these options so that permissions are applied consistently without manual policy authoring.
Conclusion
The combination of a minimal awss3bucket resource definition, provider configuration, and explicit workflow commands forms the baseline for reproducible S3 provisioning. Modern provider patterns separate bucket creation from access control, ownership, versioning, and encryption, which improves plan clarity and reduces unintended drift. The module from terraform-aws-modules/s3-bucket/aws extends this baseline to cover static web-site hosting, access logging, versioning, CORS, lifecycle rules, server-side encryption, object locking, Cross-Region Replication, and specialized log delivery policies for ELB, ALB/NLB, and WAF.
Using terraform init to download provider plugins and establish backend state, terraform plan to preview bucket, public access block, and ownership controls, and terraform apply to realize the bucket in AWS creates a predictable, auditable pipeline. The destroy path reinforces the importance of ordering: managed objects are removed before the bucket itself, reflecting the AWS requirement that buckets be empty before deletion.
Public access blocking and ownership controls replace ACL-centric examples. Older patterns that set acl = "private" are superseded by explicit public access block and ownership control resources. For content management, awss3object is the current resource for small numbers of supporting files, while bulk uploads are better handled outside Terraform.
Overall, the reference patterns show a progression from a simple bucket definition to a feature-rich module that can accommodate logging, replication, encryption, and advanced S3 types while maintaining consistency across environments.