Skip to content

Repository files navigation

Terraform Static Website/Blog Infrastructure

Terraform module for hosting a static website on AWS. Designed around the Astro framework but works with any static site generator.

Architecture

Browser → CloudFront (CDN + Lambda@Edge) → S3 (private bucket)
                  ↑
          ACM Certificate (us-east-1)
  • Traffic is served exclusively through CloudFront — the S3 bucket is private and accessed via Origin Access Control (OAC).
  • Lambda@Edge functions handle viewer filtering, prerender proxying, and response processing at the edge.
  • HTTPS is enforced via an ACM certificate with a minimum TLS version of 1.2.

AWS Components

Component Description
S3 Bucket Private bucket storing static assets; accessible only via CloudFront OAC
CloudFront Distribution CDN with IPv6, forced HTTPS, 1-year cache TTL, and Lambda@Edge associations
ACM Certificate SSL/TLS certificate provisioned in us-east-1 (required by CloudFront)
Route 53 Records Optional DNS validation records for the ACM certificate (see SSL Certificate Validation)
Lambda@Edge Module Local Terraform module (modules/lambda-at-edge/) deploying edge functions for viewer filtering, prerender proxy, geo-redirect, and response handling
IAM User & Group Deployer user (<domain>_deployer) with scoped S3 PUT and CloudFront invalidation permissions for CI/CD

Prerequisites

  • Terraform ~> 1.15
  • AWS credentials configured with sufficient permissions
  • A registered domain name

Usage

Reference this module from your root Terraform configuration:

module "static-hosting" {
  source = "github.com/krishanthisera/aws-static-hosting?ref=master"

  domain_name  = "example.com"
  bucket_name  = "example.com"
  aws_region   = "ap-southeast-2"
  common_tags  = { Project = "my-site" }
}

Then initialise and apply from your root configuration:

terraform init
terraform apply

Lambda@Edge Functions

By default, three Lambda@Edge functions are associated with the CloudFront distribution:

Event Type Function Purpose
viewer-request filter-function Filters incoming viewer requests and determines if they should be prerendered
origin-request prerender-proxy Proxies bot/crawler traffic to a prerender service for server-side rendering
origin-response response-handler Applies cache-control headers to origin responses

These functions are managed by a local Terraform module located in modules/lambda-at-edge/. The TypeScript source code for each function is in modules/lambda-at-edge/edge-functions/packages/.

Additional Functions

The module also includes a geo-redirect function (not enabled by default) that redirects users based on their geographic location.

Customizing Lambda Associations

To disable all Lambda@Edge associations, set lambda_associations to an empty list:

module "static-hosting" {
  source = "github.com/krishanthisera/aws-static-hosting?ref=master"

  # ...
  lambda_associations = []
}

To use a custom set of functions, provide your own list:

lambda_associations = [
  {
    event_type  = "viewer-request"
    lambda_name = "my-custom-function"
  },
  {
    event_type  = "origin-request"
    lambda_name = "geo-redirect"  # Enable geo-redirect
  }
]

SSL Certificate Validation

The ACM certificate is provisioned by Terraform. DNS validation behaviour is controlled by create_validation_records:

create_validation_records Behaviour
false (default) Certificate is created but Route 53 validation records are not managed. Add the CNAME records manually in your DNS provider.
true Route 53 validation records are created automatically. The hosted zone is looked up by domain_name. Terraform waits for the certificate to reach ISSUED before proceeding.

Enable automatic validation:

create_validation_records = true

CI/CD Deployer User

Terraform creates an IAM user named <domain_name>_deployer with two scoped policies:

  • S3 PUT — upload build artifacts to the assets bucket
  • CloudFront invalidation — purge the CDN cache after deployment

After terraform apply, generate an access key pair for this user and add the credentials to your CI/CD pipeline secrets.

Outputs

Output Description
assets_bucket_domain_name Regional domain name of the S3 assets bucket
cloudfront_arn ARN of the CloudFront distribution
cloudfront_domain_name CloudFront domain name (e.g. d1234.cloudfront.net)

Further Reading


Requirements

Name Version
terraform ~> 1.15
aws ~> 6.55

Providers

Name Version
aws 6.55.0
aws.us_east_1 6.55.0

Resources

Name Type
aws_acm_certificate.ssl_certificate resource
aws_acm_certificate_validation.cert_validation resource
aws_cloudfront_distribution.blog_distribution resource
aws_cloudfront_origin_access_control.blog_distribution_origin_access resource
aws_iam_group.pipeline_deployment_group resource
aws_iam_group_membership.deployment_group_membership resource
aws_iam_group_policy_attachment.cloudfront_invalidation_group_policy_attachment resource
aws_iam_group_policy_attachment.s3_put_group_policy_attachment resource
aws_iam_policy.allow_cloudfront_invalidations_policy resource
aws_iam_policy.allow_s3_put_policy resource
aws_iam_user.pipeline_deployment_user resource
aws_route53_record.domain_validation resource
aws_s3_bucket.blog_assets resource
aws_s3_bucket_acl.assets_bucket_acl resource
aws_s3_bucket_cors_configuration.assets_bucket_cors resource
aws_s3_bucket_ownership_controls.assets_bucket_acl_ownership resource
aws_s3_bucket_policy.assets_bucket_cloudfront_policy_association resource
aws_s3_bucket_public_access_block.assets_bucket_public_access resource
aws_s3_bucket_website_configuration.assets_bucket_website resource

Inputs

Name Description Type Default Required
additional_domain_aliases Additional domain aliases for the website. list(string) [] no
aws_region AWS region to deploy to. This where the S3 bucket will be created. string "ap-southeast-2" no
bucket_name The name of the bucket without the www. prefix. Normally domain_name. string n/a yes
common_tags Common tags you want applied to all components. any n/a yes
create_validation_records Whether to create Route 53 validation records for the SSL certificate. bool false no
domain_name The domain name for the website. string n/a yes
lambda_associations Lambda function associations
list(object({
event_type = string
lambda_name = string
}))
[
{
"event_type": "viewer-request",
"lambda_name": "filter-function"
},
{
"event_type": "origin-request",
"lambda_name": "prerender-proxy"
},
{
"event_type": "origin-response",
"lambda_name": "response-handler"
}
]
no
ssl_certificate_arn SSL certificate ARN for the CloudFront distribution. string "" no

Outputs

Name Description
assets_bucket_domain_name n/a
cloudfront_arn n/a
cloudfront_distribution_id n/a
cloudfront_domain_name n/a
iam_user_pipeline_deployment_user_arn n/a
iam_user_pipeline_deployment_user_name n/a

About

This repository contains a sample infrastructure as code (IaC) using Terraform to host a static website/blog. The primary use case of this setup is to host a static website using the Astro framework, though this is applicable for any static site.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages