Using Terraform to create an AWS S3 bucket with CDN
The use of AWS S3 service is very common in several projects that I usually create. Sometimes I use it to host a static website (less common now with Vercel) or I just want a more efficient way to serve assets for my application.
The process of opening the AWS console, making all the necessary configurations (creating the bucket, adding CDN, domain, etc.) is not very pleasant. That's why I liked it so much when I discovered Terraform. With it, we can automate the creation of infrastructure resources.
Prerequisites
You need to ensure a few basic steps before starting:
- Terraform installed
- AWS CLI installed
- AWS credentials configured
Creating a Terraform recipe
In a new folder, we can create a main.tf
file and start writing our Terraform recipe:
mkdir s3-terraform && cd s3-terraform
touch main.tf
Variables
In Terraform, we need to define the provider
, which is an abstraction of the API we want to use (in our case: AWS).
We will also define locals
, which are local variables. They are important to make our recipe simpler and more editable. When it is replicated for create another bucket, it will be enough to change the variables.
provider "aws" {
region = "us-east-1"
}
locals {
tags = {
Name = "Example SPA Bucket",
Environemnt = "production"
}
s3 = {
name = "mutteground-spa-bucket"
}
cdn = {
comment = "My SPA CloudFront CDN"
aliases = ["teste.mauriciomutte.dev"]
}
S3 Bucket
Creation of the S3 bucket with the name that was previously defined in the variable:
resource "aws_s3_bucket" "mutte_bucket" {
bucket = local.s3.name
force_destroy = false
tags = local.tags
}
resource "aws_s3_bucket_acl" "mutte_bucket_acl" {
bucket = aws_s3_bucket.mutte_bucket.id
acl = "private"
}
CloudFront (CDN)
A CDN (Content Delivery Network) is a network of servers that work together to deliver website content more efficiently. When you request content from a website, the CDN delivers it from the server that is closest to you. This makes the website faster and more reliable.
CDNs also use caching to store frequently accessed content on the servers, which reduces the load on the website's main server and ensures that content can be delivered quickly even during busy periods.
In this case, the CloudFront service from AWS is being used as a CDN to help deliver static files. The OAC is used to securely connect the S3 bucket with the CloudFront service.
resource "aws_cloudfront_origin_access_control" "mutte_cdn_oac" {
name = "mutte-cdn-oac"
description = "OAC for ${aws_s3_bucket.mutte_bucket.bucket}"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_cloudfront_distribution" "mutte_cdn" {
origin {
origin_id = aws_s3_bucket.mutte_bucket.bucket
origin_access_control_id = aws_cloudfront_origin_access_control.mutte_cdn_oac.id
domain_name = aws_s3_bucket.mutte_bucket.bucket_regional_domain_name
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = aws_s3_bucket.mutte_bucket.bucket
compress = true
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
}
comment = local.cdn.comment
default_root_object = "index.html"
http_version = "http2"
price_class = "PriceClass_All"
enabled = true
is_ipv6_enabled = true
retain_on_delete = false
wait_for_deployment = true
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
Setting Up Bucket Policies
To enable CloudFront to access the S3 bucket, we need to create a policy that grants it the necessary permissions
data "aws_iam_policy_document" "allow_cdn_read_s3" {
statement {
actions = ["s3:GetObject"]
resources = ["${aws_s3_bucket.mutte_bucket.arn}/*"]
principals {
type = "Service"
identifiers = ["cloudfront.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = [aws_cloudfront_distribution.mutte_cdn.arn]
}
}
data "aws_iam_policy_document" "combined" {
source_policy_documents = [
data.aws_iam_policy_document.allow_cdn_read_s3.json,
]
}
resource "aws_s3_bucket_policy" "mutte_bucket_policy_allow_cdn_read_s3" {
bucket = aws_s3_bucket.mutte_bucket.id
policy = data.aws_iam_policy_document.combined.json
}
Using Terraform
With everything configured, we can apply our Terraform recipe. Before doing so, make sure that your AWS account credentials are correctly configured
Now, we need to run the following commands:
terraform init
To install and configure what is necessary to run Terraform. It's like running an npm install command for Node.
terraform plan
This command will show what changes will be made to our infrastructure.
terraform apply
Finally, we apply the configuration to our infrastructure.
Conclusion
Automating the creation of infrastructure resources using Terraform can help save time and effort while making it easier to manage and maintain your infrastructure. By defining your infrastructure as code, you can ensure that it's consistent and reproducible, and avoid manual errors or inconsistencies.
Although the initial setup may require some effort, the benefits of using Terraform will quickly outweigh the costs in the long run. To make it even easier, I've created a GIST with the complete file, allowing you to simply copy the recipe whenever needed.