How to Set Up Cloudfront to Serve Static Content

Setting up Amazon CloudFront to serve static content involves creating a distribution, configuring your origin (e.g., S3 bucket), and updating your DNS to route traffic through CloudFront

We will be exploring out how to achieve that in this guide using terraform.

Prepare your static site

We will create a simple static site based on react and typescript.

use this command to achieve the same. You need to have npm command set up to proceed.

1
npm create vite@latest my-app -- --template react-ts

Switch to the app and install dependencies

1
2
3
cd my-app

npm i

To test the site, use this command to run the dev version.

1
npm run dev

You should be able to browse to http://localhost:5173/ and see the app running in browser.

Let’s deploy it

Build prod version of the site using this command

1
npm run build

The result is a ./dist folder with the production build.

To confirm that the static files are working fine, we can use the python -m http.server command, assuming python is installed.

1
2
3
$ python -m http.server -d ./dist

Serving HTTP on :: port 8000 (http://[::]:8000/) ...

Then visit http://localhost:8000/ and you should see the app running.

Create S3 bucket

We will use S3 to store the content.

  • We will create an s3 bucket
  • We will upload our static files (HTML, CSS, JS, images, etc.) to the S3 bucket.
  • Then we ensure our bucket policy allows access to the files. You can either make the bucket public or configure CloudFront to use an origin access control (OAC) for secure access.

Set up terraform provider

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
terraform {
  backend "s3" {
    bucket = "citizix-platform-dev-terraform-state"
    key    = "e092590-stuff/envs/dev.tfstate"
    region = "us-west-2"
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

locals {
  aws_region     = "us-west-2"
  map_migrated   = "migP3D39S7ZWQ"
  name           = "et-test-platform"
  env            = "dev"
  vpc_id         = "vpc-08f4c5661c06dcc87"
  vpc_cidr_block = "10.10.0.0/19"
  private_subnets = [
    "subnet-04a79d40938495c72",
    "subnet-05d868c7adfaaf084"
  ]
  public_subnets = [
    "subnet-0f955317ceee01151",
    "subnet-054297e268e519d34"
  ]

  tags = {
    terraform    = "yes"
    environment  = local.env
    owner        = "backend-team"
    map-migrated = local.map_migrated
  }
}

provider "aws" {
  region = local.aws_region

  # Make it faster by skipping something
  skip_metadata_api_check     = true
  skip_region_validation      = true
  skip_credentials_validation = true
}

Set up the s3 bucket using terraform

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module "onboarding_bucket" {
  source = "git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git?ref=v4.2.1"

  bucket        = local.bucket_name
  force_destroy = true

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true

  attach_policy = true
  policy = jsonencode({
    Version = "2008-10-17",
    Id      = "PolicyForCloudFrontPrivateContent",
    Statement = [
      {
        Sid       = "AllowCloudFrontServicePrincipal",
        Effect    = "Allow",
        Principal = {
          Service = "cloudfront.amazonaws.com"
        },
        Action    = "s3:GetObject",
        Resource  = "arn:aws:s3:::${local.bucket_name}/*",
        Condition = {
          StringEquals = {
            "AWS:SourceArn" = module.onboarding_cdn.cloudfront_distribution_arn
          }
        }
      }
    ]
  })

  tags = local.tags
}

Create Cloudfront Distribution

Terraform code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
module "onboarding_cdn" {
  source = "git@github.com:terraform-aws-modules/terraform-aws-cloudfront.git?ref=v3.4.1"

  aliases = [local.domain_name]

  comment             = "${local.env} citizix onboarding data"
  enabled             = true
  is_ipv6_enabled     = true
  price_class         = "PriceClass_All"
  retain_on_delete    = false
  wait_for_deployment = false

  default_root_object = "index.html"

  create_origin_access_control = true
  origin_access_control = {
    onboarding_data = {
      description      = "CloudFront access to ${local.env} citizix onboarding data"
      origin_type      = "s3"
      signing_behavior = "always"
      signing_protocol = "sigv4"
    }
  }

  origin = {
    onboarding_data = {
      domain_name           = module.onboarding_bucket.s3_bucket_bucket_regional_domain_name
      origin_access_control = "onboarding_data"
    }
  }

  default_cache_behavior = {
    target_origin_id       = "onboarding_data"
    viewer_protocol_policy = "redirect-to-https"

    allowed_methods = ["GET", "HEAD"]
    cached_methods  = ["GET", "HEAD"]
    compress        = true
    query_string    = true
  }

  custom_error_response = [{
    error_code         = 404
    response_code      = 200
    response_page_path = "/"
    }, {
    error_code         = 403
    response_code      = 200
    response_page_path = "/"
  }]

  viewer_certificate = {
    acm_certificate_arn      = aws_acm_certificate.onboarding_cert.arn
    minimum_protocol_version = "TLSv1.2_2021"
    ssl_support_method       = "sni-only"
  }

  tags = local.tags
}

Create ACM certificate

To use AWS Certificate manager, use this configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

resource "aws_acm_certificate" "onboarding_cert" {
  provider = aws.us_east_1

  domain_name       = local.domain_name
  validation_method = "DNS"

  lifecycle {
    create_before_destroy = true
  }

  tags = local.tags
}

Applying changes

Initialize terraform

1
tf init

Then apply the changes

1
terraform apply

Type yes to confirm

Copy content to the bucket

1
2
3
4
5
aws s3 rm s3://prod-citizix-static-data/ --recursive

aws s3 cp --recursive ./dist/ s3://prod-citizix-static-data/

aws s3 ls s3://prod-citizix-static-data/

Visit your url https://dev-start.citizix.com/ and you should now access your content.

You can set up cicd to deploy your changes to production.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy