Static Website Infrastructure on AWS with Terraform
Bahaa Noah • January 19, 2023
AWS
Terraform
IaC
S3
CloudFormation
Route53
ACM
As a software engineer, you likely understand the importance of automation and reproducibility in your workflow. One way to achieve this is by using Terraform, it was one of the technologies that I learned in 2022 and since then it has been the goto tool for me for any infrastructure work. In this post, I'll take you through how to use Terraform to create the necessary resources for hosting a static website on AWS, making your life a lot easier.
Required Utilities
Install these tools before proceeding:
- AWS CLI
- Terraform - Install Terraform
Configure the AWS CLI with a user that has sufficient privileges to create all the resources we will be using in this blog, and it's always a best practice to give least privileges rather than an admin access. Verify that the CLI can authenticate properly by running aws sts get-caller-identity
.
Overview
We will utilize the following services to create static website hosting infrastructure:
- AWS S3 Bucket as the hosting and storage for the website files and rearouses.
- Route 53 as DNS resolver.
- AWS Certificate Manager for securing the website and managing the ssl certificate.
- Amazon CloudFront to optimize the performance and improve security.
Using all of these services should be free of charge except for Route 53 will charge you around 00.51$ and in general if you are not in the free tier you will be charged around 3$~4$ per month based on the traffic you are getting.
Setting Up Variables
After setting up the tools, let's create the following environment variables to store commonly used values.
First things first we will create our main.tf
file and place the following in it.
terraform {
required_version = "~> 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
locals {
# change that to your domain name
domain_name = "bahaanoah.com"
# I like to tag everything that's being created by terraform
tags = {
"terraform" = true
}
}
# I will be using UAE region but feel free to use whichever you prefer
provider "aws" {
region = "me-central-1"
}
Create S3 Bucket
At this step we are going to create S3 bucket with public access and add website configuration to it using Resource: aws_s3_bucket .
Create s3.tf
file and add the following code:
resource "aws_s3_bucket" "this" {
bucket = local.domain_name
tags = local.tags
force_destroy = true
}
resource "aws_s3_bucket_policy" "allow_public_access" {
bucket = aws_s3_bucket.this.id
policy = jsonencode(
{
Statement = [
{
Action = "s3:GetObject"
Effect = "Allow"
Principal = "*"
Resource = "arn:aws:s3:::${local.domain_name}/*"
Sid = "Stmt1661600983594"
},
]
Version = "2012-10-17"
}
)
}
resource "aws_s3_bucket_versioning" "this" {
bucket = aws_s3_bucket.this.id
versioning_configuration {
status = "Disabled"
}
}
resource "aws_s3_bucket_website_configuration" "this" {
bucket = aws_s3_bucket.this.bucket
index_document {
suffix = "index.html"
}
error_document {
key = "404/index.html"
}
}
Once done go ahead and apply what we have created so far.
terraform init
terraform apply
Create Route 53 Hosted Zone
At this step will create a hosted zone to manage our dns records using Resource: aws_route53_zone.
Create route53.tf
file and place the code below:
resource "aws_route53_zone" "primary" {
name = local.domain_name
tags = local.tags
}
Go ahead and apply the changes.
terraform apply
After applying this change you should see a hosted zone with NS and SOA records created in route 53, copy the NS record values and update your domain nameservers, in my case I have my domain registered in Godaddy all I needed to do is change the default nameservers and use custom nameservers the add the NS values from AWS there.
Issue SSL Certificate
We will issue SSL certificate using AWS Certificate Manager with Resource: aws_acm_certificate and create some DNS records in route 53 to validate it at this step.
Create acm.tf
file and place the code below:
# if you are using different region from us-east-1 you will need to do that step
# because cloudfront only works with certificates issued in us-east-1
provider "aws" {
alias = "virginia"
region = "us-east-1"
}
resource "aws_acm_certificate" "this" {
domain_name = local.domain_name
validation_method = "DNS"
tags = local.tags
lifecycle {
create_before_destroy = true
}
provider = aws.virginia
}
resource "aws_route53_record" "validation" {
for_each = {
for dvo in aws_acm_certificate.this.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = aws_route53_zone.primary.zone_id
provider = aws.virginia
depends_on = [
aws_acm_certificate.this,
aws_route53_zone.primary
]
}
Go ahead and apply the changes.
terraform apply
If you connected your domain to the NS record from previous step it should take a couple of minutes and your certificate status will change to "Issued"
Create CloudFront
At this step we will create CloudFront with Resource: aws_cloudfront_distribution and connect it to the S3 Bucket we created earlier, then eventually create an A record in the hosted zone we created that's aliased to this cloudfront distribution.
Create cloudfront.tf
file and place the code below:
resource "aws_cloudfront_distribution" "this" {
origin {
domain_name = aws_s3_bucket_website_configuration.this.website_endpoint
origin_id = aws_s3_bucket_website_configuration.this.website_endpoint
connection_attempts = 3
connection_timeout = 10
custom_origin_config {
http_port = 80
https_port = 443
origin_keepalive_timeout = 5
origin_protocol_policy = "http-only"
origin_read_timeout = 30
origin_ssl_protocols = [
"TLSv1",
"TLSv1.1",
"TLSv1.2",
]
}
}
enabled = true
is_ipv6_enabled = true
aliases = [local.domain_name]
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket_website_configuration.this.website_endpoint
viewer_protocol_policy = "redirect-to-https"
compress = true
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
smooth_streaming = false
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
restrictions {
geo_restriction {
locations = []
restriction_type = "none"
}
}
tags = local.tags
viewer_certificate {
acm_certificate_arn = aws_acm_certificate.this.arn
cloudfront_default_certificate = false
minimum_protocol_version = "TLSv1.2_2021"
ssl_support_method = "sni-only"
}
depends_on = [
aws_s3_bucket.this,
aws_acm_certificate.this
]
}
Go ahead and apply the changes.
terraform apply
Once applied let's create the DNS record, open route53.tf
file and add the code below:
# update the resource name to your domain name
resource "aws_route53_record" "bahaanoah-com" {
name = local.domain_name
type = "A"
zone_id = aws_route53_zone.primary.zone_id
alias {
name = aws_cloudfront_distribution.this.domain_name
zone_id = aws_cloudfront_distribution.this.hosted_zone_id
evaluate_target_health = false
}
}
Go ahead and apply the changes.
terraform apply
If you reached that point and got everything to be working well done!, you should have your infrastructure up and running, go ahead and upload your website in the S3 bucket we created and check it out.
Cleanup
If you are not going to use it and was just trying and would like to clean up destroy everything to avoid any charges in the future.
terraform destroy
Conclusion
Terraform is a powerful tool for automating the process of creating and managing infrastructure on AWS. By using the services outlined in this post, you can easily set up a secure and performant static website that is easy to maintain and update. However, it is important to keep in mind that this is just the basic setup and there are many other options and features that can be added and configured for different needs and use cases.
As a next step, I would suggest to have a look at this github repository that's containing the code I used to create the Infrastructure for my website it's pretty much the same as we explained with some extra steps for www redirection. I would also recommend having a look at CloudFront Invalidation to manage your cache better and finally if you are working in a team I suggest to have some CI/CD pipeline to automate your infrastructure deployments with some code review process in place, find out more here Running Terraform in Automation.
Thanks for reading, I hope this was helpful. Please feel free to reach out if you need more help or have any suggestions.