DEV Community

Udoh Deborah
Udoh Deborah

Posted on

Day 67 : AWS S3 Bucket creation and management using Terraform

Before you start (checks)
• Have AWS CLI configured (aws configure) or set AWS_* env vars.
• Terraform installed (terraform -v).
• Recommended: use an S3 remote backend for state in real projects (not required for testing).

1) Create Terraform files (example structure)

day67-s3/
├─ main.tf
├─ variables.tf
├─ outputs.tf
└─ terraform.tfvars # optional: put bucket_name, region etc here

2) variables.tf

variable "region" {
  type    = string
  default = "us-east-1"
}

variable "bucket_name" {
  type = string
  # bucket names must be globally unique
  default = "my-unique-day67-bucket-<your-unique-suffix>"
}

variable "read_only_principal_arn" {
  description = "ARN of IAM user or role to grant read-only access"
  type        = string
  default     = "arn:aws:iam::<ACCOUNT_ID>:user/<USERNAME>"  # replace
}
Enter fullscreen mode Exit fullscreen mode

3) main.tf — bucket, versioning, policy, (optional) public access block

provider "aws" {
  region = var.region
}

# S3 bucket
resource "aws_s3_bucket" "site" {
  bucket = var.bucket_name

  # If you plan to host a static website, uncomment the website block and adjust
  # website {
  #   index_document = "index.html"
  # }
}

# IMPORTANT: If you want public access via ACL/policy, you may need to allow it:
# Create/Disable the account-level Block Public Access? Here we manage at bucket-level:
resource "aws_s3_bucket_public_access_block" "no_block" {
  bucket = aws_s3_bucket.site.id

  # To allow public access, set all to false. BE CAREFUL in production.
  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

# Enable versioning
resource "aws_s3_bucket_versioning" "v" {
  bucket = aws_s3_bucket.site.id

  versioning_configuration {
    status = "Enabled"
  }
}

# Make bucket objects public-read by default using ACL on the bucket (optional)
# Note: Many modern setups prefer using bucket policy only. If you set ACL,
# ensure the public access block allows it (above).
resource "aws_s3_bucket_acl" "acl" {
  bucket = aws_s3_bucket.site.id
  acl    = "public-read"
}

# Bucket policy to allow public GetObject (if you want fully public)
data "aws_iam_policy_document" "public_read" {
  statement {
    sid     = "AllowPublicGetObject"
    effect  = "Allow"
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.site.arn}/*"]
  }
}

resource "aws_s3_bucket_policy" "public_policy" {
  bucket = aws_s3_bucket.site.id
  policy = data.aws_iam_policy_document.public_read.json
}

# Bucket policy granting read-only to a specific IAM user/role
data "aws_iam_policy_document" "read_only_for_principal" {
  statement {
    sid = "AllowReadForSpecificPrincipal"
    effect = "Allow"
    principals {
      type        = "AWS"
      identifiers = [var.read_only_principal_arn]
    }
    actions = [
      "s3:GetObject",
      "s3:ListBucket"
    ]
    # ListBucket must reference bucket arn, GetObject references objects
    resources = [
      aws_s3_bucket.site.arn,
      "${aws_s3_bucket.site.arn}/*"
    ]
  }
}

resource "aws_s3_bucket_policy" "principal_policy" {
  bucket = aws_s3_bucket.site.id
  policy = data.aws_iam_policy_document.read_only_for_principal.json
}

Enter fullscreen mode Exit fullscreen mode

Notes
• The above creates both a public policy (everyone) and a principal-specific policy. In practice use one or the other depending on your requirements. If the bucket should be fully public, keep public_policy; if you want only a specific IAM user/role to read, remove public_policy and aws_s3_bucket_acl and rely only on the principal_policy.
• Modern AWS best practice: avoid public buckets unless necessary. Instead, grant access to specific principals or use CloudFront with signed URLs.

4) outputs.tf

output "bucket_name" {
  value = aws_s3_bucket.site.bucket
}

output "bucket_arn" {
  value = aws_s3_bucket.site.arn
}

output "website_endpoint" {
  value = aws_s3_bucket.site.website_endpoint
  description = "Empty unless you enabled static website hosting."
  sensitive   = false
}
Enter fullscreen mode Exit fullscreen mode

5) Initialize & apply (commands)

cd day67-s3
terraform init
terraform validate
terraform plan -out plan.tfplan
terraform apply "plan.tfplan"
# or terraform apply -auto-approve
Enter fullscreen mode Exit fullscreen mode

Watch outputs for bucket name/ARN. If apply fails with public access errors, check AWS Account Public Access Block settings — account-level block may prevent public policy/ACL.

6) Test public read access
1. Upload a sample file (AWS CLI):

aws s3 cp sample.txt s3://<your-bucket-name>/sample.txt --acl public-read
Enter fullscreen mode Exit fullscreen mode
  1. Open in browser:
https://<your-bucket-name>.s3.amazonaws.com/sample.txt
Enter fullscreen mode Exit fullscreen mode

If publicly accessible you’ll see content.

If you used static website hosting, URL is:

http://<your-bucket-name>.s3-website-<region>.amazonaws.com/index.html
Enter fullscreen mode Exit fullscreen mode

7) Verify versioning

Use AWS CLI:

aws s3api get-bucket-versioning --bucket <your-bucket-name>
# Expected: Status: Enabled
Enter fullscreen mode Exit fullscreen mode

Upload an object, then upload again; list versions:

aws s3api list-object-versions --bucket <your-bucket-name> --prefix sample.txt
Enter fullscreen mode Exit fullscreen mode

8) Clean up (optional)

# remove objects and versions first, then destroy
aws s3 rm s3://<your-bucket-name> --recursive
terraform destroy -auto-approve
Enter fullscreen mode Exit fullscreen mode

S3 buckets with versioning require special handling to delete versions before bucket deletion.

Top comments (0)