Skip to main content

Command Palette

Search for a command to run...

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

Published
โ€ข4 min read
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.

More from this blog

B

Build With Rajesh

31 posts