Deploy a Static Website on AWS using Terraform
Use AWS's powerful and scalable infrastructure to serve a high-performance static site using Terraforms modules for easy deployment and asset uploading.
When it comes to deploying static websites, AWS offers a powerful and scalable infrastructure. By leveraging Terraform, an Infrastructure as Code (IaC) tool, we can automate the deployment process while maintaining full control of our infrastructure. In this guide, we’ll walk you through deploying a high-performance static site using Terraform modules on AWS.
Why Terraform and AWS?
Terraform simplifies the process of provisioning and managing infrastructure by allowing you to define everything in code. AWS offers key services such as S3, CloudFront, and Route53, which together can host and deliver a secure, high-performing website with global reach. The combination of these services, automated via Terraform, ensures that your site will load quickly and reliably for users across the world.
Prerequisites
Before starting, ensure you have the following:
An AWS account
AWS CLI installed and configured with appropriate IAM permissions
Terraform installed on your local machine
Domain name registered in AWS Route 53
Terraform Modules and Configuration
We will use several Terraform modules to provision AWS services, including:
S3 for storing website files.
CloudFront for content delivery and caching.
Route 53 for DNS management.
ACM (AWS Certificate Manager) for SSL certificates to ensure HTTPS.
Step-by-Step Deployment
1. Set up the Domain and SSL Certificate
The main.tf
file initiates the configuration by defining the necessary AWS providers and setting up the required SSL certificates using the ACM module.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 3.37.0"
configuration_aliases = [aws.default, aws.acm]
}
}
}
locals {
final_path = var.hostname != null ? "${var.hostname}.${var.domain}" : var.domain
alternative_paths = [for h in var.alternative_hostnames : "${h}.${var.domain}"]
}
data "aws_route53_zone" "hosted_zone" {
provider = aws.default
name = var.domain
}
/*
* Certificate
*/
module "certificate" {
source = "commitdev/zero/aws//modules/certificate"
providers = {
aws = aws.acm
}
zone_name = data.aws_route53_zone.hosted_zone.name
domain_name = local.final_path
alternative_names = local.alternative_paths
}
This section provisions a domain and sets up SSL certificates, enabling secure HTTPS access to your website.
2. Configure S3 for Website Hosting and CDN
We use S3 to store the static site content and configure it to redirect any non-secure HTTP traffic to HTTPS. The CloudFront CDN will provide content distribution globally ensuring highly performant load times for the deployed site.
The website.tf
file defines the S3 and CloudFront configurations.
/*
* Website S3 Endpoints
*/
resource "aws_s3_bucket" "website_bucket" {
provider = aws.default
bucket = local.final_path
lifecycle {
prevent_destroy = true
}
}
resource "aws_s3_bucket_website_configuration" "website_bucket_config" {
provider = aws.default
bucket = aws_s3_bucket.website_bucket.bucket
index_document {
suffix = "index.html"
}
error_document {
key = "404.html"
}
}
resource "aws_s3_bucket_public_access_block" "website_bucket_acl" {
provider = aws.default
bucket = aws_s3_bucket.website_bucket.bucket
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
data "aws_iam_policy_document" "public_read_get_object" {
provider = aws.default
statement {
sid = "PublicReadGetObject"
effect = "Allow"
principals {
type = "AWS"
identifiers = ["*"]
}
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.website_bucket.arn}/*"]
}
}
resource "aws_s3_bucket_policy" "website_bucket_policy" {
provider = aws.default
bucket = aws_s3_bucket.website_bucket.bucket
policy = data.aws_iam_policy_document.public_read_get_object.json
}
/*
* Website CDN
*/
resource "aws_cloudfront_distribution" "website_bucket_cdn" {
provider = aws.default
origin {
domain_name = aws_s3_bucket_website_configuration.website_bucket_config.website_endpoint
origin_id = "website-s3-${local.final_path}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
enabled = true
is_ipv6_enabled = true
price_class = "PriceClass_100"
default_cache_behavior {
target_origin_id = "website-s3-${local.final_path}"
viewer_protocol_policy = "redirect-to-https"
compress = false
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
aliases = [local.final_path]
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = module.certificate.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
}
/*
* Website DNS Routing
*/
resource "aws_route53_record" "website_record" {
provider = aws.default
name = local.final_path
type = "A"
zone_id = data.aws_route53_zone.hosted_zone.id
alias {
evaluate_target_health = false
name = aws_cloudfront_distribution.website_bucket_cdn.domain_name
zone_id = aws_cloudfront_distribution.website_bucket_cdn.hosted_zone_id
}
}
/*
* Website S3 Objects
*/
module "local_files" {
source = "hashicorp/dir/template"
base_dir = var.upload_path
}
resource "aws_s3_object" "website_file" {
provider = aws.default
depends_on = [module.local_files]
for_each = module.local_files.files
bucket = aws_s3_bucket.website_bucket.bucket
key = each.key
content_type = each.value.content_type
source = each.value.source_path
content = each.value.content
etag = each.value.digests.md5
}
3. Set Up Route 53 DNS Redirects
Route 53 is used to manage the domain name and point it to the CloudFront distribution. Redirects are utilized to ensure that alternatives paths are routed to the correct location.
/*
* Redirect S3 Endpoints
*/
resource "aws_s3_bucket" "redirect_bucket" {
provider = aws.default
bucket = "redirects.${var.domain}"
}
resource "aws_s3_bucket_website_configuration" "redirect_bucket_config" {
provider = aws.default
bucket = aws_s3_bucket.redirect_bucket.bucket
redirect_all_requests_to {
host_name = local.final_path
protocol = "https"
}
}
/*
* Redirect CDN
*/
resource "aws_cloudfront_distribution" "redirect_bucket_cdn" {
provider = aws.default
origin {
domain_name = aws_s3_bucket_website_configuration.redirect_bucket_config.website_endpoint
origin_id = "redirect-s3-${var.domain}"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1", "TLSv1.1", "TLSv1.2"]
}
}
enabled = true
is_ipv6_enabled = true
price_class = "PriceClass_100"
default_cache_behavior {
target_origin_id = "redirect-s3-${var.domain}"
viewer_protocol_policy = "redirect-to-https"
compress = false
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
aliases = local.alternative_paths
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = module.certificate.certificate_arn
ssl_support_method = "sni-only"
minimum_protocol_version = "TLSv1.2_2021"
}
}
/*
* Redirect DNS Routing
*/
resource "aws_route53_record" "redirect_record" {
provider = aws.default
for_each = toset(local.alternative_paths)
name = each.key
type = "A"
zone_id = data.aws_route53_zone.hosted_zone.id
alias {
evaluate_target_health = false
name = aws_cloudfront_distribution.redirect_bucket_cdn.domain_name
zone_id = aws_cloudfront_distribution.redirect_bucket_cdn.hosted_zone_id
}
}
This part creates DNS records to point your domain to the CloudFront distribution, ensuring that visitors to your site are routed correctly.
4. Setting Vartiables for Module
To make this module reusable, variables are exposed to allow configurations for the specific deployment attributes.
variable "domain" {
description = "The domain where content will be hosted at (ex: mainDomain.com)"
type = string
}
variable "hostname" {
description = "The hostname where content will be hosted at (ex: www)"
type = string
nullable = true
default = null
}
variable "alternative_hostnames" {
description = "The hostnames to redirect from (ex: [missing, legacy])"
type = list(string)
nullable = false
default = []
}
variable "upload_path" {
description = "The path to upload content from (ex:./website)"
type = string
default = "dir-that-dne"
}
5. Using the Module
The static-website
module is now ready to be utilized in a stack.
module "static-website" {
providers = {
aws.default = aws.prod,
aws.acm = aws.prod-acm
}
source = "../../modules/static-website"
domain = "example.com"
alternative_hostnames = ["www"]
upload_path = "./site-content"
}
Conclusion
By using Terraform, you can quickly deploy and manage a highly performant static site on AWS. With the power of S3, CloudFront, Route 53, and ACM, your site will not only be secure but also load swiftly for users around the globe. Plus, the infrastructure is fully automated and easily replicable using Terraform, making future updates or scaling as simple as possible.
Give it a try, and take advantage of AWS’s robust infrastructure to deliver a top-tier static website!