• Skip to main content
  • Skip to primary sidebar
BMA

BeMyAficionado

Inspire Affection

A Step-by-Step Guide to Deploy a Static Website with CloudFront and S3 Using CDK Behind A Custom Domain

February 6, 2024 by varunshrivastava Leave a Comment

This article is intended for developers who want to deploy a Single Page Application on Cloudfront and make it accessible via a custom domain. I will try to make it systematic so its easier to pick and implement directly. I will try my best to explain why each step is necessary. Sometimes I have found that things don’t work and usually there’s a puny reason behind it. Therefore, knowing how things are working behind the scene could help resolve these issues quickly.

Table of Contents

  • Ground Setup
    • Cloudfront
    • Route 53 DNS
  • Infrastructure as Code using CDK
    • Create a folder
    • Install CDK CLI
    • Initialize Project
    • Step 1: Create S3 Bucket to Host Single Page Application
    • Step 2: Add Policy to Allow Read Operation on S3 bucket
    • Step 3: Creating an Origin Access Identity (OAI) and Grant Read Permission
    • Step 4: Create SSL Certificate
      • Certificate Request
      • CNAME Record Creation
      • Validation Process
      • Certificate Issuance
    • Step 5: Create Cloudfront Website Distribution
    • Step 6: Create Hosted Zone and Point to Cloudfront Alias
    • Step 7: Bucket Deployment to Serve Single Page Application
    • Complete Infrastructure Code
    • Step 8: Deploy
  • Conclusion

Ground Setup

It’s always good to create a goal as to what exactly you are trying to achieve from this. And the best way is to do this is by defining the infrastructure on a white board or paper. Here’s the infrastructure that you will build using CDK.

Cloudfront

Cloudfront is a service offerred by AWS that sits between you and your webserver to serve you content with minimal latency. You can think of it as the nearest cache location also known as edge locations. In case the content is not cached in the nearest edge location, the cloudfront fetches the content from the origin server and returns to the user while caching it at the same time.

Route 53 DNS

This is a highly scalable Domain Name Server web service. You can use this service to perform 3 main functions:

  • Domain Registration
  • DNS routing
  • Health Checking

In this tutorial, we will be using it for routing traffic to our deployed cloudfront destribution.

Infrastructure as Code using CDK

Before starting with the code the prerequisite is that you should have CDK code base setup and ready to go.

Easiest way to setup cdk project is by using CDK CLI. This is provided by AWS to make your life easier.

Create a folder

mkdir cdk-bma

Install CDK CLI

Navigate to the above folder and run following command:

npm install -g aws-cdk             # install latest version

Initialize Project

cdk init app --language typescript

After running the above command, it will setup the initial project for you to start with right away. Following is the initial project setup:

.
├── bin
├── cdk.out
├── lib
├── node_modules
└── test

Now, just to get started, we will only write the infrastructure code in the lib/cdk-bma-stack.ts file. This is the main stack that is created for us to get started.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';

export class CdkBmaStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // The code that defines your stack goes here

    // example resource
    // const queue = new sqs.Queue(this, 'CdkBmaQueue', {
    //   visibilityTimeout: cdk.Duration.seconds(300)
    // });
  }
}

If you want to create more stacks or nested stacks you can take a look into the bin/cdk-bma.ts file for reference.

Since the code supports typescript, so you can use any typescript construct to structure your infrastructure. And that is the power behind CDK compared to other infra tools like terraform. It is actual code that you write, no templating, pure code. You can use all your object oriented knowledge as well while structure your stacks and different modules.

Let’s dive into the code now.

Step 1: Create S3 Bucket to Host Single Page Application

const domainName = 'mywebsitedomain.com';
const prefix = "mywebsiteprefix";

export class CdkBmaStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
        const websiteBucket = new aws_s3.Bucket(this, `${prefix}-bucket`, {
            bucketName: `${prefix}-website-bucket`,
            removalPolicy: cdk.RemovalPolicy.DESTROY,
            websiteIndexDocument: 'index.html',
        });
.
.
.
}

Step 2: Add Policy to Allow Read Operation on S3 bucket

.
.

        websiteBucket.addToResourcePolicy(new PolicyStatement({
            sid: 's3BucketPublicRead',
            effect: Effect.ALLOW,
            actions: ['s3:GetObject'],
            principals: [new ServicePrincipal('cloudfront.amazonaws.com')],
            resources: [`${websiteBucket.bucketArn}/*`]
        }))
.
.

Step 3: Creating an Origin Access Identity (OAI) and Grant Read Permission

An OAI is used by CloudFront to securely access the content in your Amazon S3 bucket. Essentially, it acts as a virtual user identity that CloudFront uses to request files from your bucket.

After executing this code, CloudFront can use the OAI to securely access and serve files stored in the specified S3 bucket (websiteBucket). This setup is commonly used for hosting static websites or content with CloudFront, where you want to restrict direct access to the S3 bucket and only allow access through CloudFront.

.. 
        const oai = new aws_cloudfront.OriginAccessIdentity(this, "OriginAccessIdentity");
        websiteBucket.grantRead(oai);
..

Step 4: Create SSL Certificate

        const certificate = new aws_certificatemanager.Certificate(this, 'SiteCertificate', {
            domainName: domainName,
            validation: aws_certificatemanager.CertificateValidation.fromDns(hostedZone),
        });

This is a 4 step process that CDK does for you with just 3 lines of code. But its good to understand what it does internally to make certificate available.

Certificate Request

  • When you request a certificate from ACM and choose DNS validation, ACM generates two unique strings for each domain name that the certificate will cover. These strings are used to construct a CNAME record. One string serves as the name (or host) for the CNAME record, and the other as the value (or points to) the CNAME record.

CNAME Record Creation

  • ACM provides you with the specific CNAME record details:
    • Name: This is constructed using one of the unique strings generated by ACM and your domain name. It looks something like _abc123.example.com, where _abc123 is the unique string and example.com is your domain.
    • Value: This points to another unique string generated by ACM, indicating a domain under ACM’s control, such as _xyz456.acm-validations.aws.
  • You then create this CNAME record in your DNS configuration. If you’re using Amazon Route 53 and the domain is managed there, ACM can often add the record automatically if given permission.

Validation Process

  • ACM periodically queries the DNS system for the CNAME record you were instructed to create. ACM knows exactly which CNAME record to look for because it generated the unique strings that compose the name and value of the record.
  • By resolving the CNAME record to the expected value, ACM can confirm that you control the domain names for which you’re requesting the certificate. This is because only someone with control over the domain’s DNS settings could create the specific CNAME record ACM requested.

Certificate Issuance

  • Once ACM successfully validates your control of the domain by finding and matching the CNAME record, the certificate’s status changes to “Issued,” and it becomes available for use in AWS services like Elastic Load Balancing, Amazon CloudFront, and API Gateway.

Step 5: Create Cloudfront Website Distribution

The code is pretty self explanatory. It does following:

  • Instantiate a new Cloudfront Distribution
  • Configure S3 bucket as the origin to fetch and serve content from the bucket
  • Define error configurations
  • Sets up viewer protocol policy to redirect all requests to HTTPS
  • Configure cloudfront to use SSL Certificate (created in the prior step)
        const cloudFrontDistribution = new aws_cloudfront.CloudFrontWebDistribution(this, `cloud-front-distribution`,{
            originConfigs: [
                {
                    s3OriginSource: {
                        s3BucketSource: websiteBucket,
                        originAccessIdentity: oai
                    },
                    behaviors: [{isDefaultBehavior: true}]
                }
            ],
            errorConfigurations: [
                {
                    errorCode: 404,
                    responseCode: 200,
                    responsePagePath: '/404.html',
                },
            ],
            viewerProtocolPolicy: aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            viewerCertificate: aws_cloudfront.ViewerCertificate.fromAcmCertificate(certificate, {
                aliases: [domainName],
                securityPolicy: aws_cloudfront.SecurityPolicyProtocol.TLS_V1_2_2019,
                sslMethod: aws_cloudfront.SSLMethod.SNI
            })
        });

Step 6: Create Hosted Zone and Point to Cloudfront Alias

    const hostedZone = new route53.PublicHostedZone(this, 'MyHostedZone', {
      zoneName: domainName,
    });

    
    new route53.ARecord(this, 'CloudFrontARecord', {
      zone: hostedZone,
      target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(cloudFrontDistribution)),
      recordName: 'www'
    });

Step 7: Bucket Deployment to Serve Single Page Application

        new aws_s3_deployment.BucketDeployment(this, `react-app-deployment`, {
            destinationBucket: websiteBucket,
            sources: [aws_s3_deployment.Source.asset("../build")],
            distribution: cloudFrontDistribution,
            distributionPaths: ["/*"]
        });

Complete Infrastructure Code

import * as cdk from 'aws-cdk-lib';
import {
    aws_apigateway,
    aws_certificatemanager,
    aws_cloudfront,
    aws_iam,
    aws_lambda,
    aws_route53,
    aws_route53_targets,
    aws_s3,
    aws_s3_deployment
} from 'aws-cdk-lib';
import {Construct} from 'constructs';
import {Effect, PolicyStatement, ServicePrincipal} from "aws-cdk-lib/aws-iam";

const domainName = 'mywebsitedomain.com';
const prefix = "mywebsiteprefix";

export class CdkBmaStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
        const websiteBucket = new aws_s3.Bucket(this, `${prefix}-bucket`, {
            bucketName: `${prefix}-website-bucket`,
            removalPolicy: cdk.RemovalPolicy.DESTROY,
            websiteIndexDocument: 'index.html',
        });

        const oai = new aws_cloudfront.OriginAccessIdentity(this, "OriginAccessIdentity");
        websiteBucket.grantRead(oai);

        websiteBucket.addToResourcePolicy(new PolicyStatement({
            sid: 's3BucketPublicRead',
            effect: Effect.ALLOW,
            actions: ['s3:GetObject'],
            principals: [new ServicePrincipal('cloudfront.amazonaws.com')],
            resources: [`${websiteBucket.bucketArn}/*`]
        }))

        // Create an SSL certificate
        const certificate = new aws_certificatemanager.Certificate(this, 'SiteCertificate', {
            domainName: domainName,
            validation: aws_certificatemanager.CertificateValidation.fromDns(hostedZone),
        });

        const cloudFrontDistribution = new aws_cloudfront.CloudFrontWebDistribution(this, `cloud-front-distribution`,{
            originConfigs: [
                {
                    s3OriginSource: {
                        s3BucketSource: websiteBucket,
                        originAccessIdentity: oai
                    },
                    behaviors: [{isDefaultBehavior: true}]
                }
            ],
            errorConfigurations: [
                {
                    errorCode: 404,
                    responseCode: 200,
                    responsePagePath: '/404.html',
                },
            ],
            viewerProtocolPolicy: aws_cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            viewerCertificate: aws_cloudfront.ViewerCertificate.fromAcmCertificate(certificate, {
                aliases: [domainName],
                securityPolicy: aws_cloudfront.SecurityPolicyProtocol.TLS_V1_2_2019,
                sslMethod: aws_cloudfront.SSLMethod.SNI
            })
        });

        const hostedZone = new route53.PublicHostedZone(this, 'MyHostedZone', {
          zoneName: domainName,
        });

    
        new route53.ARecord(this, 'CloudFrontARecord', {
          zone: hostedZone,
          target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(cloudFrontDistribution)),
          recordName: 'www'
        });

       
        new aws_s3_deployment.BucketDeployment(this, `react-app-deployment`, {
            destinationBucket: websiteBucket,
            sources: [aws_s3_deployment.Source.asset("../build")],
            distribution: cloudFrontDistribution,
            distributionPaths: ["/*"]
        });

    }
}

Step 8: Deploy

Its time to deploy our stack and see it in action.

cdk deploy CdkBmaStack

Conclusion

In this article we covered all the steps required to deploy any Single Page Application to S3 and host it via Cloudfront behind a DNS. We wrote the entire code in CDK using typescript to see the infra come to life. I would strongly recommend CDK when you are starting a new project because of the shear ease it brings in terms of managing infrastructure. All the infrastructure stays with you at all time.

Let me know how you find this article and definitely comment below if you face any problem. I would love to sort any of your queries and improve this article over time.

Related

Filed Under: Blogging, Tutorials

Primary Sidebar

Subscribe to Blog via Email

Do you enjoy the content? Feel free to leave your email with me to receive new content straight to your inbox. I'm an engineer, you can trust me :)

Join 874 other subscribers

Latest Podcasts

Recent Posts

  • Is The Cosmos a Vast Computation?
  • Building Semantic Search for E-commerce Using Product Embeddings and OpenSearch
  • Leader Election with ZooKeeper: Simplifying Distributed Systems Management
  • AWS Serverless Event Driven Data Ingestion from Multiple and Diverse Sources
  • A Step-by-Step Guide to Deploy a Static Website with CloudFront and S3 Using CDK Behind A Custom Domain

Recent Comments

  • Varun Shrivastava on Deploy Lambda Function and API Gateway With Terraform
  • Vaibhav Shrivastava on Deploy Lambda Function and API Gateway With Terraform
  • Varun Shrivastava on Should Girls Wear Short Clothes?
  • D on Should Girls Wear Short Clothes?
  • disqus_X5PikVsRAg on Basic Calculator Leetcode Problem Using Object-Oriented Programming In Java

Categories

  • Blogging
  • Cooking
  • Fashion
  • Finance & Money
  • Programming
  • Reviews
  • Software Quality Assurance
  • Technology
  • Travelling
  • Tutorials
  • Web Hosting
  • Wordpress N SEO

Archives

  • November 2024
  • September 2024
  • July 2024
  • April 2024
  • February 2024
  • November 2023
  • June 2023
  • May 2023
  • April 2023
  • August 2022
  • May 2022
  • April 2022
  • February 2022
  • January 2022
  • November 2021
  • September 2021
  • August 2021
  • June 2021
  • May 2021
  • April 2021
  • February 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020
  • June 2020
  • May 2020
  • April 2020
  • February 2020
  • December 2019
  • November 2019
  • October 2019
  • August 2019
  • July 2019
  • June 2019
  • May 2019
  • April 2019
  • March 2019
  • January 2019
  • November 2018
  • October 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • March 2018
  • February 2018
  • January 2018
  • December 2017
  • November 2017
  • October 2017
  • September 2017
  • August 2017
  • July 2017
  • June 2017
  • May 2017
  • April 2017
  • March 2017
  • February 2017
  • January 2017
  • December 2016
  • November 2016
  • October 2016
  • September 2016
  • August 2016
  • July 2016
  • June 2016
  • May 2016

Tags

Affordable Hosting (4) algorithms (4) amazon (3) aoc-2020 (7) believe in yourself (4) best (4) database (4) earn money blogging (5) education (4) elementary sorting algorithms (4) experience (3) fashion (4) finance (6) Financial Freedom (7) food (7) friends (3) goals (5) google (5) india (10) indian cuisine (5) indian education system (4) java (16) life (16) life changing (4) love (4) make money (3) microservices (9) motivation (4) oops (4) podcast (6) poor education system (4) principles of microservices (5) problem-solving (7) programmer (5) programming (28) python (5) reality (3) seo (6) spring (3) success (10) success factor (4) technology (4) top 5 (7) typescript (3) wordpress (7)

Copyright © 2025 · Be My Aficionado · WordPress · Log in

Go to mobile version