Static Website Hosting on AWS using Terraform (Mini Project-1)

Every day with Terraform feels like Iโm unlocking a new layer of how real infrastructure is built.
Day 14 was special โ because instead of just learning concepts, I built something end-to-end and production-style.
๐ A static website hosted on AWS using Terraform, powered by S3 + CloudFront, with proper security and global delivery.
This mini project helped me understand how Terraform moves from learning syntax to building real systems.
Project Overview
The goal of this project was simple:
Deploy a secure, globally accessible static website using Infrastructure as Code.
Architecture Flow
User (Internet)
โ
CloudFront Distribution (CDN + HTTPS)
โ
S3 Bucket (Static Website Files)
Components Used
S3 Bucket โ Website Storage
Hosts all static website files:
HTML
CSS
JavaScript
Images
Acts as the origin for CloudFront
resource "aws_s3_bucket" "cloud_front_s3_bucket" {
bucket = var.s3_bucket_name
}
Secure Public Access Configuration
Instead of making the bucket public, I blocked all public access and allowed access only via CloudFront.
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.cloud_front_s3_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
This ensures:
โ No direct S3 access
โ Only CloudFront can fetch objects
CloudFront Origin Access Control (OAC)
This was the most important learning in the project.
Instead of using legacy OAI, I implemented Origin Access Control (OAC) โ the modern and recommended approach by AWS.
Why OAC?
๐ Prevents direct access to S3
โ๏ธ Uses SigV4 signed requests
๐ Production-grade security
๐ Best practice for new CloudFront setups
OAC Configuration
resource "aws_cloudfront_origin_access_control" "oac" {
name = "oac"
description = "cloudfront to s3 access"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
S3 Bucket Policy (Allow Only CloudFront)
The bucket policy explicitly allows only CloudFront to read objects.
resource "aws_s3_bucket_policy" "bucket_policy" {
bucket = aws_s3_bucket.cloud_front_s3_bucket.id
depends_on = [aws_s3_bucket_public_access_block.example]
policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Sid = "AllowCloudFrontServicePrincipalReadOnly",
Effect = "Allow",
Principal = {
Service = "cloudfront.amazonaws.com"
},
Action = ["s3:GetObject"],
Resource = "${aws_s3_bucket.cloud_front_s3_bucket.arn}/*",
Condition = {
StringEquals = {
"AWS:SourceArn" = aws_cloudfront_distribution.s3_distribution.arn
}
}
}]
})
}
This ensures secure, controlled access โ no shortcuts.
๐ Uploading Website Files Automatically
One of the most satisfying parts was uploading all website files automatically using Terraform.
resource "aws_s3_object" "object" {
bucket = aws_s3_bucket.cloud_front_s3_bucket.bucket
for_each = fileset("${path.module}/www", "**/*")
key = each.value
source = "${path.module}/www/${each.value}"
etag = filemd5("${path.module}/www/${each.value}")
content_type = lookup({
"html" = "text/html",
"css" = "text/css",
"js" = "application/javascript",
"png" = "image/png",
"jpg" = "image/jpeg",
"svg" = "image/svg+xml"
}, split(".", each.value)[length(split(".", each.value)) - 1], "application/octet-stream")
}
What I Learned Here
Terraform can deploy infra + content
Correct MIME types are crucial
No manual uploads needed ๐ฏ
CloudFront Distribution (CDN + HTTPS)
CloudFront delivers the website globally with HTTPS enabled.
resource "aws_cloudfront_distribution" "s3_distribution" {
origin {
domain_name = aws_s3_bucket.cloud_front_s3_bucket.bucket_regional_domain_name
origin_access_control_id = aws_cloudfront_origin_access_control.oac.id
origin_id = local.s3_origin_id
}
enabled = true
default_root_object = "index.html"
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
viewer_protocol_policy = "redirect-to-https"
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
Final Output
Terraform outputs the CloudFront domain name:
output "named_domain" {
value = aws_cloudfront_distribution.s3_distribution.domain_name
}
Once applied, I could open the URL and see my website live on AWS ๐
๐ Project Structure
Terraform/
โโโ main.tf
โโโ variables.tf
โโโ outputs.tf
โโโ README.md
โโโ www/
โโโ index.html
โโโ style.css
โโโ script.js
Key Takeaways
โ Terraform can manage real production setups
โ OAC is critical for S3 + CloudFront security
โ Static hosting doesnโt mean โsimpleโ โ it means clean design
โ Infrastructure + content can be fully automated
๐บ Video That Helped Me Understand this concept:
Final Thoughts
This mini project gave me confidence.
Terraform is no longer just a tool Iโm learning โ
Itโs becoming a tool I can build real systems with.
On to the next challenge ๐ช
Whatโs Next?
Iโm planning to take this project one step further by adding ACM (AWS Certificate Manager) and Route 53 for a custom domain setup, once I get a domain name.
That will complete the journey from a basic static site to a fully production-ready, custom-domain website.




