CloudFront to serve Static website hosted on S3 using Terraform

Raghav D
9 min readMay 21, 2021

Amazon CloudFront is a fast content delivery network (CDN) service that securely delivers data, videos, applications, and APIs to customers globally with low latency, high transfer speeds, all within a developer-friendly environment.

How CloudFront works ???

We have a website, it should serve the content to all over world. users will get the content from the site, so users may face some delay in serving the content according to the Geo locations.

If we make this content commonly used and does not much change will be cached in edge locations of regions near to the user. So end user will not face much latency in serving the website content.

Edge locations:
A site that CloudFront uses to cache copies of your content for faster delivery to users at any location.

Origin: Source of files to be shared. This could be S3, EC2, ELB, Route53.

Distribution: Edge Location collection that caches files on Origin.

  • Web Distribution
  • RTMP Distribution

Now we will serve the static website hosted on S3 through CDN, You can host static website on S3 by following link: https://raghavendar-d.medium.com/s3-bucket-as-static-website-using-terraform-c2a554b9aeba

while hosting static website on S3 we will make some changes, we will update the policy as show below (Here my S3 bucket: raghav.terraform-tutorials.com)

bucket access policy
{"Version": "2008-10-17","Id": "PolicyForCloudFrontPrivateContent","Statement": [{"Sid": "1","Effect": "Allow","Principal": {"AWS": "*"},"Action": "s3:GetObject","Resource": "arn:aws:s3:::raghav.terraform-tutorials.com/*"}]}

Host the website.

We will push this content to CDN using terraform as show below

main.tf

#s3 bucket detailsdata "aws_s3_bucket" "mywebsite_bucket" {bucket = "raghav.terraform-tutorials.com"}resource "aws_cloudfront_origin_access_identity" "origin_access_identity" {comment = "access-identity-raghav.terraform-tutorials.com"}resource "aws_cloudfront_distribution" "s3_distribution" {origin {domain_name = "raghav.terraform-tutorials.com.s3.amazonaws.com"origin_id   = "raghav.terraform-tutorials.com"s3_origin_config {origin_access_identity = "${aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path}"}}enabled             = trueis_ipv6_enabled     = truecomment             = "Some comment"default_root_object = "index.html"default_cache_behavior {allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]cached_methods   = ["GET", "HEAD"]target_origin_id = "raghav.terraform-tutorials.com"forwarded_values {query_string = falsecookies {forward = "none"}}viewer_protocol_policy = "allow-all"min_ttl                = 0default_ttl            = 3600max_ttl                = 86400}# Cache behavior with precedence 0ordered_cache_behavior {path_pattern     = "/content/immutable/*"allowed_methods  = ["GET", "HEAD", "OPTIONS"]cached_methods   = ["GET", "HEAD", "OPTIONS"]target_origin_id = "raghav.terraform-tutorials.com"forwarded_values {query_string = falseheaders      = ["Origin"]cookies {forward = "none"}}min_ttl                = 0default_ttl            = 86400max_ttl                = 31536000compress               = trueviewer_protocol_policy = "redirect-to-https"}# Cache behavior with precedence 1ordered_cache_behavior {path_pattern     = "/content/*"allowed_methods  = ["GET", "HEAD", "OPTIONS"]cached_methods   = ["GET", "HEAD"]target_origin_id = "raghav.terraform-tutorials.com"forwarded_values {query_string = falsecookies {forward = "none"}}min_ttl                = 0default_ttl            = 3600max_ttl                = 86400compress               = trueviewer_protocol_policy = "redirect-to-https"}price_class = "PriceClass_200"restrictions {#locations to serve the contentgeo_restriction {restriction_type = "whitelist"locations        = ["US", "CA", "GB", "DE", "IN"]}}tags = {Environment = "production"}viewer_certificate {cloudfront_default_certificate = true}}

variable.tf

variable "stage" {}variable "project_name" {}variable "region" {}

profile.tf

provider "aws" {profile = "aws_profile"}

Now we will execute the terraform commands

terraform init

terraform plan
output will be:

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:# aws_cloudfront_distribution.s3_distribution will be created
+ resource "aws_cloudfront_distribution" "s3_distribution" {
+ arn = (known after apply)
+ caller_reference = (known after apply)
+ comment = "Some comment"
+ default_root_object = "index.html"
+ domain_name = (known after apply)
+ enabled = true
+ etag = (known after apply)
+ hosted_zone_id = (known after apply)
+ http_version = "http2"
+ id = (known after apply)
+ in_progress_validation_batches = (known after apply)
+ is_ipv6_enabled = true
+ last_modified_time = (known after apply)
+ price_class = "PriceClass_200"
+ retain_on_delete = false
+ status = (known after apply)
+ tags = {
+ "Environment" = "production"
}
+ trusted_signers = (known after apply)
+ wait_for_deployment = true
+ default_cache_behavior {
+ allowed_methods = [
+ "DELETE",
+ "GET",
+ "HEAD",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
]
+ cached_methods = [
+ "GET",
+ "HEAD",
]
+ compress = false
+ default_ttl = 3600
+ max_ttl = 86400
+ min_ttl = 0
+ target_origin_id = "raghav.terraform-tutorials.com"
+ trusted_signers = (known after apply)
+ viewer_protocol_policy = "allow-all"
+ forwarded_values {
+ headers = (known after apply)
+ query_string = false
+ query_string_cache_keys = (known after apply)
+ cookies {
+ forward = "none"
+ whitelisted_names = (known after apply)
}
}
}
+ ordered_cache_behavior {
+ allowed_methods = [
+ "GET",
+ "HEAD",
+ "OPTIONS",
]
+ cached_methods = [
+ "GET",
+ "HEAD",
+ "OPTIONS",
]
+ compress = true
+ default_ttl = 86400
+ max_ttl = 31536000
+ min_ttl = 0
+ path_pattern = "/content/immutable/*"
+ target_origin_id = "raghav.terraform-tutorials.com"
+ viewer_protocol_policy = "redirect-to-https"
+ forwarded_values {
+ headers = [
+ "Origin",
]
+ query_string = false
+ query_string_cache_keys = (known after apply)
+ cookies {
+ forward = "none"
}
}
}
+ ordered_cache_behavior {
+ allowed_methods = [
+ "GET",
+ "HEAD",
+ "OPTIONS",
]
+ cached_methods = [
+ "GET",
+ "HEAD",
]
+ compress = true
+ default_ttl = 3600
+ max_ttl = 86400
+ min_ttl = 0
+ path_pattern = "/content/*"
+ target_origin_id = "raghav.terraform-tutorials.com"
+ viewer_protocol_policy = "redirect-to-https"
+ forwarded_values {
+ headers = (known after apply)
+ query_string = false
+ query_string_cache_keys = (known after apply)
+ cookies {
+ forward = "none"
}
}
}
+ origin {
+ domain_name = "raghav.terraform-tutorials.com.s3.amazonaws.com"
+ origin_id = "raghav.terraform-tutorials.com"
+ s3_origin_config {
+ origin_access_identity = (known after apply)
}
}
+ restrictions {
+ geo_restriction {
+ locations = [
+ "CA",
+ "DE",
+ "GB",
+ "IN",
+ "US",
]
+ restriction_type = "whitelist"
}
}
+ viewer_certificate {
+ cloudfront_default_certificate = true
+ minimum_protocol_version = "TLSv1"
}
}
# aws_cloudfront_origin_access_identity.origin_access_identity will be created
+ resource "aws_cloudfront_origin_access_identity" "origin_access_identity" {
+ caller_reference = (known after apply)
+ cloudfront_access_identity_path = (known after apply)
+ comment = "access-identity-raghav.terraform-tutorials.com"
+ etag = (known after apply)
+ iam_arn = (known after apply)
+ id = (known after apply)
+ s3_canonical_user_id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.

everything is looks good, we will apply our plan

terraform apply

terraform apply
var.project_name
Enter a value: raghav-terraform
var.region
Enter a value: us-east-1
var.stage
Enter a value: terraform
provider.aws.region
The region where AWS operations will take place. Examples
are us-east-1, us-west-2, etc.
Enter a value: us-east-1An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:# aws_cloudfront_distribution.s3_distribution will be created
+ resource "aws_cloudfront_distribution" "s3_distribution" {
+ arn = (known after apply)
+ caller_reference = (known after apply)
+ comment = "Some comment"
+ default_root_object = "index.html"
+ domain_name = (known after apply)
+ enabled = true
+ etag = (known after apply)
+ hosted_zone_id = (known after apply)
+ http_version = "http2"
+ id = (known after apply)
+ in_progress_validation_batches = (known after apply)
+ is_ipv6_enabled = true
+ last_modified_time = (known after apply)
+ price_class = "PriceClass_200"
+ retain_on_delete = false
+ status = (known after apply)
+ tags = {
+ "Environment" = "production"
}
+ trusted_signers = (known after apply)
+ wait_for_deployment = true
+ default_cache_behavior {
+ allowed_methods = [
+ "DELETE",
+ "GET",
+ "HEAD",
+ "OPTIONS",
+ "PATCH",
+ "POST",
+ "PUT",
]
+ cached_methods = [
+ "GET",
+ "HEAD",
]
+ compress = false
+ default_ttl = 3600
+ max_ttl = 86400
+ min_ttl = 0
+ target_origin_id = "raghav.terraform-tutorials.com"
+ trusted_signers = (known after apply)
+ viewer_protocol_policy = "allow-all"
+ forwarded_values {
+ headers = (known after apply)
+ query_string = false
+ query_string_cache_keys = (known after apply)
+ cookies {
+ forward = "none"
+ whitelisted_names = (known after apply)
}
}
}
+ ordered_cache_behavior {
+ allowed_methods = [
+ "GET",
+ "HEAD",
+ "OPTIONS",
]
+ cached_methods = [
+ "GET",
+ "HEAD",
+ "OPTIONS",
]
+ compress = true
+ default_ttl = 86400
+ max_ttl = 31536000
+ min_ttl = 0
+ path_pattern = "/content/immutable/*"
+ target_origin_id = "raghav.terraform-tutorials.com"
+ viewer_protocol_policy = "redirect-to-https"
+ forwarded_values {
+ headers = [
+ "Origin",
]
+ query_string = false
+ query_string_cache_keys = (known after apply)
+ cookies {
+ forward = "none"
}
}
}
+ ordered_cache_behavior {
+ allowed_methods = [
+ "GET",
+ "HEAD",
+ "OPTIONS",
]
+ cached_methods = [
+ "GET",
+ "HEAD",
]
+ compress = true
+ default_ttl = 3600
+ max_ttl = 86400
+ min_ttl = 0
+ path_pattern = "/content/*"
+ target_origin_id = "raghav.terraform-tutorials.com"
+ viewer_protocol_policy = "redirect-to-https"
+ forwarded_values {
+ headers = (known after apply)
+ query_string = false
+ query_string_cache_keys = (known after apply)
+ cookies {
+ forward = "none"
}
}
}
+ origin {
+ domain_name = "raghav.terraform-tutorials.com.s3.amazonaws.com"
+ origin_id = "raghav.terraform-tutorials.com"
+ s3_origin_config {
+ origin_access_identity = (known after apply)
}
}
+ restrictions {
+ geo_restriction {
+ locations = [
+ "CA",
+ "DE",
+ "GB",
+ "IN",
+ "US",
]
+ restriction_type = "whitelist"
}
}
+ viewer_certificate {
+ cloudfront_default_certificate = true
+ minimum_protocol_version = "TLSv1"
}
}
# aws_cloudfront_origin_access_identity.origin_access_identity will be created
+ resource "aws_cloudfront_origin_access_identity" "origin_access_identity" {
+ caller_reference = (known after apply)
+ cloudfront_access_identity_path = (known after apply)
+ comment = "access-identity-raghav.terraform-tutorials.com"
+ etag = (known after apply)
+ iam_arn = (known after apply)
+ id = (known after apply)
+ s3_canonical_user_id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.Warning: Interpolation-only expressions are deprecatedon main.tf line 24, in resource "aws_cloudfront_distribution" "s3_distribution":
24: origin_access_identity = "${aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path}"
Terraform 0.11 and earlier required all non-constant expressions to be
provided via interpolation syntax, but this pattern is now deprecated. To
silence this warning, remove the "${ sequence from the start and the }"
sequence from the end of this expression, leaving just the inner expression.
Template interpolation syntax is still used to construct strings from
expressions when the template includes multiple interpolation sequences or a
mixture of literal strings and interpolations. This deprecation applies only
to templates that consist entirely of a single interpolation sequence.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yesaws_cloudfront_origin_access_identity.origin_access_identity: Creating...
aws_cloudfront_origin_access_identity.origin_access_identity: Creation complete after 3s [id=E2OKPP9KJEMKMO]
aws_cloudfront_distribution.s3_distribution: Creating...
aws_cloudfront_distribution.s3_distribution: Still creating... [10s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [20s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [30s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [40s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [50s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [1m0s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [1m10s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [1m20s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [1m30s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [1m40s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [1m50s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [2m0s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [2m10s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [2m20s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [2m30s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [2m40s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [2m50s elapsed]
aws_cloudfront_distribution.s3_distribution: Still creating... [3m0s elapsed]
aws_cloudfront_distribution.s3_distribution: Creation complete after 3m3s [id=EWU2M7VC6TMBY]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Now check for the terraform.tfstate file

domain name in state file

domain name: d2xp90d785s9b5.cloudfront.net

will serve the content, lets test the CDN domain

content served with CDN domain

We have hosted our static website on S3 through CDN

References:

  1. https://aws.amazon.com/cloudfront/
  2. https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-s3-amazon-cloudfront-a-match-made-in-the-cloud/
  3. https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_distribution

--

--