Co-authored with AI
TL;DR
🚀 What: A Terraform module that deploys production-ready static sites (S3 + CloudFront) with enterprise features that other modules miss.
🔥 Key Features:
- ✅ Automatic cache invalidation (built-in Lambda system)
- ✅ Cross-account CloudFront logging (enterprise compliance)
- ✅ Wildcard domain support (perfect for PR previews)
- ✅ Subfolder root objects (automatic index.html serving)
⚡ Setup Time: 5 minutes vs 2-3 hours manual setup
📦 Get Started: source = "thu-san/static-site/aws"
Building and deploying static websites on AWS seems straightforward until you need enterprise features like automatic cache invalidation, cross-account logging, or wildcard domain support. Most existing solutions require manual cache clearing, separate invalidation tools, or complex custom setups that break when requirements evolve.
I've created a Terraform module that solves these pain points with built-in enterprise features that "just work" out of the box. Let me show you what makes this different and how it can save you weeks of implementation time.
What Makes This Module Different
🔄 Automatic Cache Invalidation (The Game Changer)
Unlike other static site modules that require manual invalidation or separate tools, this module includes a built-in Lambda-based cache invalidation system. When you upload files to S3, CloudFront automatically updates - no more stale content or manual purging.
module "static_site" {
source = "thu-san/static-site/aws"
# Enable automatic cache invalidation
enable_cache_invalidation = true
invalidation_mode = "custom"
# Smart invalidation patterns
invalidation_path_mappings = [
{
source_pattern = "^images/.*"
invalidation_paths = ["/images/*"]
description = "Invalidate all images on any image upload"
},
{
source_pattern = "^(index\\.html|home\\.html)$"
invalidation_paths = ["/*"]
description = "Full cache clear on homepage changes"
}
]
}
📊 Cross-Account CloudFront Log Delivery
Perfect for enterprise environments where you need centralized logging across AWS accounts. Send CloudFront access logs to your security team's account without complex IAM gymnastics.
module "static_site" {
source = "thu-san/static-site/aws"
# Deliver logs to centralized logging account
log_delivery_destination_arn = "arn:aws:logs:us-east-1:SECURITY-ACCOUNT:delivery-destination:central-logs"
}
🌐 Wildcard Domain Support + Subfolder Root Objects
Built-in support for wildcard certificates and automatic index.html serving from subdirectories - essential for PR preview deployments and multi-tenant architectures.
Quick Start: Basic Setup
The simplest setup requires just two required parameters:
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "us_east_1"
region = "us-east-1"
}
module "static_site" {
source = "thu-san/static-site/aws"
version = "~> 1.2"
s3_bucket_name = "my-awesome-site-bucket"
cloudfront_distribution_name = "my-awesome-site"
providers = {
aws = aws
aws.us_east_1 = aws.us_east_1
}
}
Why two providers? CloudFront requires ACM certificates to be in us-east-1
, so we need a dedicated provider for certificate management regardless of your main region.
Outputs you'll get:
-
cloudfront_distribution_domain_name
- Your site's CloudFront URL -
bucket_id
- S3 bucket name for uploads -
acm_certificate_arn
- SSL certificate ARN
Advanced Use Case: PR Preview Deployments
Here's where this module really shines. Setting up PR previews with wildcard domains and intelligent routing:
Click to see the full PR preview setup
# CloudFront function for intelligent PR routing
resource "aws_cloudfront_function" "pr_router" {
name = "pr-router"
runtime = "cloudfront-js-2.0"
publish = true
code = <<-EOT
function handler(event) {
var request = event.request;
var host = request.headers.host.value;
// Extract PR number from subdomain (e.g., pr123.dev.example.com)
var prMatch = host.match(/^pr(\d+)\./);
if (prMatch) {
var prNumber = prMatch[1];
request.uri = '/pr' + prNumber + request.uri;
}
// Auto-append index.html for directory requests
if (request.uri.endsWith('/')) {
request.uri += 'index.html';
}
return request;
}
EOT
}
module "static_site" {
source = "thu-san/static-site/aws"
s3_bucket_name = "my-pr-preview-bucket"
cloudfront_distribution_name = "pr-preview-site"
# Wildcard domain support
domain_names = [
"dev.example.com",
"*.dev.example.com" # pr123.dev.example.com, pr456.dev.example.com
]
hosted_zone_name = "example.com"
# Attach the PR routing function
cloudfront_function_associations = [{
event_type = "viewer-request"
function_arn = aws_cloudfront_function.pr_router.arn
}]
# Built-in subfolder index serving
subfolder_root_object = "index.html"
providers = {
aws = aws
aws.us_east_1 = aws.us_east_1
}
}
Result:
- Main branch content at
dev.example.com
- PR #123 content at
pr123.dev.example.com
(serves from/pr123/
folder) - PR #456 content at
pr456.dev.example.com
(serves from/pr456/
folder) - Automatic SSL certificates for all subdomains
Under the Hood: Architecture & Security
Security-First Design
- Private S3 bucket with all public access blocked
- Origin Access Control (OAC) for CloudFront-only access
- TLS 1.2 minimum enforcement
- Versioning enabled for data protection
Smart Cache Invalidation Architecture
When enable_cache_invalidation = true
, the module creates:
- SQS Queue - Batches S3 events for cost-efficient processing
- Lambda Function - Processes events and creates CloudFront invalidations
- Dead Letter Queue - Handles failed invalidations for debugging
- IAM Roles - Least-privilege access for all components
The Lambda function is smart about costs - it deduplicates paths and uses wildcards to minimize CloudFront invalidation charges.
Why Not Just Use [Other Module/Manual Setup]?
Feature | This Module | Manual Setup | Other Modules |
---|---|---|---|
Cache Invalidation | ✅ Built-in | ❌ Separate tool needed | ❌ Usually missing |
Cross-Account Logs | ✅ Native support | ❌ Complex IAM setup | ❌ Not supported |
Wildcard Domains | ✅ Full support | ❌ Manual certificate management | ⚠️ Limited support |
Setup Time | ⏱️ 5 minutes | ⏱️ 2-3 hours | ⏱️ 30+ minutes |
Maintenance | 🔄 Self-managing | 🛠️ Manual updates needed | 🛠️ Partial automation |
Getting Started
Installation Options
From Terraform Registry:
module "static_site" {
source = "thu-san/static-site/aws"
version = "~> 1.2"
# ... your config
}
From OpenTofu Registry:
module "static_site" {
source = "thu-san/static-site/aws"
version = "~> 1.2"
# ... your config
}
From GitHub (latest):
module "static_site" {
source = "git::https://github.com/thu-san/terraform-aws-static-site.git"
# ... your config
}
Essential Configuration Tips
- Always use version constraints to avoid breaking changes
- Test in a separate AWS account first - especially the cache invalidation feature
- Enable logging early - it helps with debugging and compliance
- Use meaningful tags - the module propagates them to all resources
Examples & Documentation
Check out the examples directory for:
- Basic static site setup
- Custom domain configuration
- PR preview deployments
- Cross-account logging setup
- Cache invalidation patterns
What's Next?
I'm actively maintaining this module and would love your feedback! If you:
- ⭐ Find it useful, please star the GitHub repository
- 📋 Check it out on Terraform Registry or OpenTofu Registry
- 🐛 Find issues, open a GitHub issue
- 💡 Have feature ideas, start a discussion
- 🤝 Want to contribute, PRs are welcome
The module has comprehensive tests and follows Terraform best practices. It's production-ready and already being used by several teams for everything from marketing sites to internal documentation.
What challenges are you facing with static site deployments on AWS? Have you tried this module, or are you planning to? Let me know in the comments!
Top comments (0)