AWS S3 with Terraform: Create Buckets Step by Step

Published: 2025-08-23
7 min read
Share:

AWS S3 with Terraform is one of the most practical ways to automate cloud storage management using Infrastructure as Code (IaC). Instead of manually creating buckets through the AWS Console, you can define everything in code, track changes in Git, and deploy the same configuration across multiple environments.

If you're already using Terraform for AWS infrastructure, S3 buckets are usually among the first resources you'll automate. In this guide, you'll learn how to create S3 buckets, upload objects, manage access with bucket policies, and keep infrastructure state consistent.

Before continuing, make sure Terraform is installed and configured on your system. If you're new to Terraform, start with our guides on Installing Terraform and Getting Started with Terraform on AWS.

Why Use AWS S3 with Terraform?

Creating a single bucket manually is simple. Managing buckets across development, staging, and production environments is where Terraform becomes valuable.

Using AWS S3 with Terraform provides:

  • Consistent infrastructure across environments
  • Version-controlled storage configurations
  • Repeatable deployments
  • Easier collaboration between teams
  • Reduced risk of manual configuration errors

Terraform uses HashiCorp Configuration Language (HCL) to define the desired infrastructure state. When you run terraform apply, Terraform compares the desired state with the actual infrastructure and performs only the required changes.

For a deeper understanding of Terraform configuration syntax, see HCL Basics.

Create an AWS S3 Bucket with Terraform

The primary resource used for bucket creation is aws_s3_bucket.

Example S3 Bucket Configuration

resource "aws_s3_bucket" "finance" {
  bucket = "finance-21092020"

  tags = {
    Description = "Used by finance and payroll team"
  }
}

Run the following commands:

terraform init
terraform plan
terraform apply

Terraform will create the bucket in your AWS account.

Pro Tip

S3 bucket names must be globally unique across all AWS accounts. If Terraform returns a bucket name conflict error, include environment-specific identifiers such as:

  • dev
  • staging
  • production
  • AWS account ID

Example:

bucket = "finance-prod-123456789012"

For complete resource options, refer to the official Terraform AWS Provider Documentation.

Upload Files to an S3 Bucket

After creating the bucket, you can upload objects into it.

Terraform currently supports object management through the AWS provider's S3 object resources.

Example Object Upload

resource "aws_s3_object" "invoice" {
  bucket  = aws_s3_bucket.finance.id
  key     = "invoice.csv"
  content = "Invoice data content here"
}

Important arguments include:

  • bucket – Target S3 bucket
  • key – Object name inside the bucket
  • content – Content to upload

When Terraform applies the configuration, the object is uploaded automatically.

Pro Tip

Avoid managing large application artifacts with Terraform.

Most production teams use:

  • AWS CLI
  • AWS SDKs
  • CI/CD pipelines

Terraform works best for infrastructure provisioning, while deployment tools handle application artifacts.

Control S3 Bucket Access with Terraform Policies

Creating a bucket is only part of the job. Access control is equally important.

AWS S3 bucket policies allow you to define exactly who can access the bucket and what actions they can perform.

Step 1: Reference an Existing IAM Group

Assume an IAM group named finance-analysts already exists in AWS.

Since Terraform did not create it, use a data source to read its information.

data "aws_iam_group" "finance_data" {
  group_name = "finance-analysts"
}

Terraform data sources allow you to reference existing infrastructure without importing it into state.

Learn more about Terraform data sources.

Step 2: Create a Bucket Policy

The recommended approach is to generate JSON using jsonencode().

resource "aws_s3_bucket_policy" "finance_policy" {
  bucket = aws_s3_bucket.finance.id

  policy = jsonencode({
    Version = "2012-10-17"

    Statement = [
      {
        Sid    = "FinanceTeamAccess"
        Effect = "Allow"

        Principal = {
          AWS = data.aws_iam_group.finance_data.arn
        }

        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:ListBucket"
        ]

        Resource = [
          aws_s3_bucket.finance.arn,
          "${aws_s3_bucket.finance.arn}/*"
        ]
      }
    ]
  })
}

Security Note

Many tutorials use s3:* permissions for simplicity.

In production environments, follow the principle of least privilege and grant only the permissions that users or applications actually require.

This reduces security risks and aligns with AWS security best practices.

For IAM fundamentals, see AWS IAM with Terraform and AWS IAM Policies with Terraform.

Enable S3 Bucket Versioning

Versioning helps protect against accidental deletions and overwrites.

You can enable it using the aws_s3_bucket_versioning resource.

resource "aws_s3_bucket_versioning" "finance_versioning" {
  bucket = aws_s3_bucket.finance.id

  versioning_configuration {
    status = "Enabled"
  }
}

Once enabled, Amazon S3 stores multiple versions of the same object.

This feature is commonly used for:

  • Backup protection
  • Recovery from accidental deletion
  • Compliance requirements
  • Change tracking

Learn more from the official Amazon S3 Documentation.

Complete AWS S3 Terraform Example

The following example combines bucket creation, versioning, object upload, and bucket policy configuration.

resource "aws_s3_bucket" "finance" {
  bucket = "finance-prod-123456789012"
}

resource "aws_s3_bucket_versioning" "finance_versioning" {
  bucket = aws_s3_bucket.finance.id

  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_s3_object" "invoice" {
  bucket  = aws_s3_bucket.finance.id
  key     = "invoice.csv"
  content = "Invoice data content here"
}

data "aws_iam_group" "finance_data" {
  group_name = "finance-analysts"
}

resource "aws_s3_bucket_policy" "finance_policy" {
  bucket = aws_s3_bucket.finance.id

  policy = jsonencode({
    Version = "2012-10-17"

    Statement = [
      {
        Sid    = "FinanceTeamAccess"
        Effect = "Allow"

        Principal = {
          AWS = data.aws_iam_group.finance_data.arn
        }

        Action = [
          "s3:GetObject",
          "s3:PutObject",
          "s3:ListBucket"
        ]

        Resource = [
          aws_s3_bucket.finance.arn,
          "${aws_s3_bucket.finance.arn}/*"
        ]
      }
    ]
  })
}

How Terraform State Tracks Your S3 Infrastructure

Terraform stores infrastructure information in a state file named:

terraform.tfstate

This file allows Terraform to understand:

  • What resources already exist
  • What changes are required
  • What resources should be created, updated, or deleted

A common mistake is creating or modifying AWS resources directly in the AWS Console after Terraform has deployed them.

When infrastructure changes happen outside Terraform, configuration drift can occur. Drift makes Terraform plans less reliable and harder to maintain.

To understand state management in detail, read:

For team environments, storing state locally is not recommended.

Use a remote backend instead.

Learn how to configure Remote Backends Using AWS S3 and implement Remote State and State Locking.

Best Practices for AWS S3 with Terraform

Follow these practices when managing S3 buckets with Terraform:

  • Use unique and predictable bucket naming conventions.
  • Enable versioning for critical workloads.
  • Apply least-privilege IAM permissions.
  • Store Terraform state in a remote backend.
  • Use tags consistently across resources.
  • Avoid manual AWS Console changes whenever possible.
  • Keep Terraform and AWS provider versions updated.
  • Review execution plans before every deployment.

Frequently Asked Questions (FAQs)

Can I use Terraform to upload large files to S3?

Yes, but Terraform is not designed to manage large application artifacts. For large files, AWS CLI, SDKs, or CI/CD pipelines are generally a better choice.

How do I manage versioning on S3 buckets using Terraform?

Use the aws_s3_bucket_versioning resource and set the versioning status to Enabled.

Is it secure to hardcode content in aws_s3_object?

For small examples, it is acceptable. In real projects, use the source argument to upload local files instead of embedding content directly inside Terraform code.

Can I manage bucket lifecycle rules with Terraform?

Yes. Terraform supports lifecycle configurations for object expiration, storage class transitions, and cleanup automation through dedicated S3 lifecycle resources.

What is the best way to grant access to an S3 bucket?

Use IAM roles, IAM groups, and bucket policies together while following the principle of least privilege. Avoid granting unnecessary permissions.

Final Thoughts

AWS S3 with Terraform allows you to manage cloud storage the same way you manage the rest of your infrastructure: through version-controlled, repeatable code.

By defining buckets, object uploads, versioning, and access policies in Terraform, teams can reduce manual effort, improve consistency, and deploy storage resources with confidence.

Free Engineering ToolsNEW

8 free, 100% client-side tools for developers — no signup, no data uploads.

Explore all tools