Hosting a Static DevOps Resources Website on AWS with Terraform: A Complete Guide

Introduction

In today’s fast-paced DevOps landscape, having a centralized hub for essential tools and resources can be invaluable. Whether you’re onboarding new team members, sharing best practices, or simply maintaining a quick reference guide for cloud and DevOps tools, a well-organized static website can be the perfect solution.

In this post, I’ll walk you through how to create and deploy a beautiful, responsive static website that showcases DevOps tools and cloud platforms using TerraformAWS S3, and CloudFront. This approach is cost-effective, scalable, and follows infrastructure-as-code best practices.

Why Static Hosting with Terraform?

Before we dive into the technical details, let’s understand why this approach makes sense:

Cost-Effective: Static hosting on S3 is incredibly cheap. You’re looking at pennies per month for most use cases.

Zero Maintenance: No servers to manage, no databases to maintain, no security patches to apply.

Scalability: S3 and CloudFront automatically handle traffic spikes without any intervention.

Infrastructure as Code: Using Terraform makes your infrastructure reproducible, versionable, and auditable.

CDN Performance: CloudFront ensures your content is delivered quickly to users worldwide.

Security: With Origin Access Identity (OAI), your S3 bucket is never exposed to the internet directly.

Architecture Overview

Here’s what we’re building:

┌─────────────────┐
│ CloudFront │ (Global CDN, HTTPS)
│ Distribution │
└────────┬────────┘

┌────────▼────────┐
│ S3 Bucket │ (Stores index.html)
│ eu-central-1 │
└─────────────────┘

Key Components:

  1. S3 Bucket — Stores the static HTML files in the eu-central-1 region
  2. CloudFront Distribution — Global CDN that caches and distributes your content
  3. Origin Access Identity (OAI) — Securely grants CloudFront access to S3
  4. IAM Policy — Controls permissions between CloudFront and S3

Step 1: Create the Terraform Configuration

First, let’s create our infrastructure as code. Create a file called main.tf:

terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "eu-central-1"
}# Get current AWS account ID
data "aws_caller_identity" "current" {}# S3 bucket for hosting
resource "aws_s3_bucket" "website" {
bucket = "devops-links-website-${data.aws_caller_identity.current.account_id}" tags = {
Name = "DevOps Links Website"
Environment = "production"
}
}# Block all public access
resource "aws_s3_bucket_public_access_block" "website" {
bucket = aws_s3_bucket.website.id block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}# Enable versioning
resource "aws_s3_bucket_versioning" "website" {
bucket = aws_s3_bucket.website.id versioning_configuration {
status = "Enabled"
}
}# CloudFront OAI
resource "aws_cloudfront_origin_access_identity" "website" {
comment = "OAI for DevOps Links Website"
}# S3 Bucket Policy
resource "aws_s3_bucket_policy" "website" {
bucket = aws_s3_bucket.website.id policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "CloudFrontGetObject"
Effect = "Allow"
Principal = {
AWS = aws_cloudfront_origin_access_identity.website.iam_arn
}
Action = "s3:GetObject"
Resource = "${aws_s3_bucket.website.arn}/*"
}
]
}) depends_on = [aws_s3_bucket_public_access_block.website]
}# Upload HTML file
resource "aws_s3_object" "index" {
bucket = aws_s3_bucket.website.id
key = "index.html"
source = "${path.module}/index.html"
content_type = "text/html"
etag = filemd5("${path.module}/index.html") depends_on = [aws_s3_bucket_policy.website]
}# CloudFront Distribution
resource "aws_cloudfront_distribution" "website" {
enabled = true
is_ipv6_enabled = true
default_root_object = "index.html" origin {
domain_name = aws_s3_bucket.website.bucket_regional_domain_name
origin_id = "S3Origin" s3_origin_config {
origin_access_identity = aws_cloudfront_origin_access_identity.website.cloudfront_access_identity_path
}
} default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "S3Origin" forwarded_values {
query_string = false
cookies {
forward = "none"
}
} viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
compress = true
} restrictions {
geo_restriction {
restriction_type = "none"
}
} viewer_certificate {
cloudfront_default_certificate = true
} depends_on = [aws_s3_object.index]
}# Outputs
output "website_url" {
description = "Website URL"
value = "https://${aws_cloudfront_distribution.website.domain_name}"
}output "s3_bucket_name" {
description = "S3 bucket name"
value = aws_s3_bucket.website.id
}

Step 2: Create the HTML File

Create an index.html file with your DevOps resources. Here’s a sample structure:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DevOps & Cloud Resources</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%);
min-height: 100vh;
padding: 40px 20px;
}

header {
text-align: center;
color: #000;
margin-bottom: 50px;
background: rgba(255, 255, 255, 0.95);
padding: 40px;
border-radius: 15px;
}

.categories {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 30px;
max-width: 1200px;
margin: 0 auto;
}

.category {
background: white;
border-radius: 15px;
padding: 30px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<header>
<h1>DevOps & Cloud Resources</h1>
<p>Essential tools for cloud infrastructure</p>
</header>

<div class="categories">
<!-- Add your categories here -->
</div>
</body>
</html>

Step 3: Deploy with Terraform

Now for the exciting part! Deploy your infrastructure:

# Initialize Terraform
terraform init

# Preview the changes
terraform plan

# Apply the configuration
terraform apply

Terraform will create all the necessary AWS resources. Once it completes, you’ll see the output:

website_url = "https://d2c0tr4dt8mwgk.cloudfront.net"
s3_bucket_name = "devops-links-website-123456789"

Visit that URL, and your website is live! 🚀

Key Features of This Setup

1. Security First

  • S3 bucket is completely blocked from public access
  • Only CloudFront can access it via Origin Access Identity
  • HTTPS by default through CloudFront

2. Performance

  • CloudFront caches your content globally
  • Default TTL of 3600 seconds (1 hour)
  • Automatic compression of responses

3. Cost Optimization

  • Typically costs less than $1/month
  • S3 storage: ~$0.023 per GB
  • CloudFront egress: ~$0.085 per GB (first 10TB)

4. Versioning

  • S3 versioning enabled for disaster recovery
  • Easy rollback if needed

5. Infrastructure as Code

  • Entire setup in version control
  • Reproducible across environments
  • Easy to maintain and update

Repo URL : https://github.com/muralikrishna-sunkara/s3-static-website

Troubleshooting Common Issues

AccessDenied Errors

If you encounter AccessDenied when accessing CloudFront:

  1. Ensure default_root_object is set in CloudFront distribution
  2. Verify the S3 bucket policy grants the OAI proper permissions
  3. Invalidate CloudFront cache:
aws cloudfront create-invalidation \
--distribution-id YOUR_DISTRIBUTION_ID \
--paths "/*"
  1. Wait 5–10 minutes for the changes to propagate

File Upload Issues

Make sure your index.html file is in the same directory as your Terraform files, and the path in aws_s3_object is correct.

Next Steps: Enhancements

Add Custom Domain: Attach a custom domain to CloudFront Add SSL Certificate: Use AWS Certificate Manager for HTTPS Add Error Pages: Configure 404 error handling Monitoring: Set up CloudFront metrics in CloudWatch

Conclusion

We’ve successfully created a secure, scalable, and cost-effective static website hosting solution using Terraform and AWS. This approach is perfect for:

  • DevOps team resource hubs
  • Documentation sites
  • Personal portfolios
  • Project showcases
  • Internal knowledge bases

The beauty of Infrastructure as Code is that you can now version control your entire infrastructure, make changes with confidence, and replicate this setup across different environments or organizations.

Want to extend this? Try adding:

  • A custom domain name
  • AWS Certificate Manager for custom SSL certificates
  • Multiple HTML pages with routing
  • GitHub Actions for automated deployments

Resources

Happy Infrastructure Coding! 🚀

If you found this helpful, please share it with your team. Questions? Drop them in the comments below!

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Post

The 5 Most Common Kubernetes Issues (And How to Fix Them)

A DevOps guide to troubleshooting the errors that keep you up at night, from CrashLoopBackOff to networking black holes. Kubernetes is the undisputed king of container orchestration. It’s powerful, scalable, and the de facto standard for modern cloud-native applications. But with great power comes great complexity. If you’re in DevOps, you know that a significant part of […]

Your App’s Bouncer: A No-BS Guide to AWS WAF

Look, I’ve been doing this for over a decade. If there’s one thing I know, it’s that the internet is a dumpster fire of malicious requests, and your beautiful, lovingly-crafted application is the target. You can have the cleanest code in the world (you don’t) and the most robust infrastructure (it’s not), but at 3 […]

🛑 The Troubleshooting Playbook: Resolving Common Kubernetes Node Failures

Kubernetes is the engine of modern cloud infrastructure, but even the best engines sometimes sputter. When a node (the worker machine running your containers) fails, your pods get evicted, and your application availability plummets. Mastering node troubleshooting means quickly identifying the issue behind the cryptic status messages. Here is a playbook covering the most common […]