Deploy a Static Website on S3 with a Custom Domain Using AWS CDK

Paul Allies
5 min readApr 17, 2024

--

(without Route 53)

In an earlier blog post, I walked you through the process using the AWS console. Now, I want to highlight just how much simpler it is to deploy S3 static website hosting with a CDN and your own domain name using CDK.

CDK, or the Cloud Development Kit, is a powerful tool that allows you to define and deploy AWS cloud infrastructure using programming languages like TypeScript, Python, and Java.

Since we’re not utilising Route 53, I recommend setting up an SSL certificate outside of the CDK stack. Afterwards, we’ll reference this certificate’s ARN when configuring the CDN to link with the custom domain name. This approach ensures a seamless integration and avoids unnecessary repetition.

Requesting a Certificate in AWS Console:

  1. Sign in to the AWS Management Console and access your account.
  2. Switch Region to “US East (N. Virginia)” (us-east-1) from the top right corner to ensure you are in the correct region for ACM.
  3. Open AWS Certificate Manager (ACM)
  4. Request a Certificate by clicking on the “Request” button.
  5. Enter Domain Name and click “Request”.
  6. Open Certificate from the list, and Store CNAME Details: Note the CNAME name and values provided.

Adding a DNS Record:

  1. Log in to your domain registrar’s website.
  2. Go to the DNS settings or management section.
  3. Add a CNAME record: Use the AWS-provided name in the “Name” or “Host” field and the CNAME value in the “Value” field.
  4. Save the changes.

Awaiting Validation:

  1. Return to the AWS ACM console.
  2. Refresh the page to check the validation status of your certificate. It might take a while.
  3. Once validated and the certificate status changes to “Issued,” you are not ready to use the certificate.

That’s it! You’ve successfully requested a certificate in the AWS Console, added the DNS record in your domain registrar

Let’s proceed with setting up a CDK project to create and deploy the stack of resources.

When done, save the arn of the Certificate to be used later

Create your CDK Project

$ mkdir s3-static-website
$ cd s3-static-website
$ cdk init app --language typescript

Check out AWS docs for getting started with CDK

Edit the stack

Open the stack file under the lib directory

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

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

}
}

Step 1: Provision a Bucket

Create a bucket to be destroyed when we delete the stack, and make the bucket publicly accessible

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { aws_s3 as s3 } from 'aws-cdk-lib';

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

const DOMAIN_NAME = 'example.com';

// Create an S3 bucket to host the static website
const bucket = new s3.Bucket(this, 'StaticWebsiteBucket', {
bucketName: DOMAIN_NAME,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
publicReadAccess: true,
blockPublicAccess: new s3.BlockPublicAccess({
blockPublicAcls: false,
blockPublicPolicy: false,
ignorePublicAcls: false,
restrictPublicBuckets: false
}),
versioned: true,
websiteIndexDocument: 'index.html',
});


}
}

Step 2: Add the website directory to your project

Create a directory called “website” in the root of the CDK project containing an index.html file

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<title>Document</title>
</head>
<body>
My Static Website
</body>
</html>

Step 3: Deploy Files to the Bucket

Next, our goal is to deploy a local directory containing our website file assets to the bucket.

import { aws_s3_deployment as s3deploy } from 'aws-cdk-lib';

...

// Deploy website files to S3 bucket
new s3deploy.BucketDeployment(this, 'DeployWebsite', {
sources: [s3deploy.Source.asset('./website')], // Path to the directory containing the index.html file
destinationBucket: bucket,
});

Step 4. Create a Reference to the SSL certificate

import { aws_certificatemanager as acm } from 'aws-cdk-lib';

...

// Create a reference to SSL certificate in ACM
const certificateArn = 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012';
const certificate = acm.Certificate.fromCertificateArn(this, 'StaticSiteCertificate', certificateArn);

Step 5: Create the CloudFront CDN

import { aws_cloudfront as cloudfront } from 'aws-cdk-lib';
import { aws_cloudfront_origins as origins } from 'aws-cdk-lib';

...

// Create a CloudFront distribution
const cdn = new cloudfront.Distribution(this, 'StaticSiteCDN', {
domainNames: [DOMAIN_NAME],
defaultBehavior: {
origin: new origins.HttpOrigin(bucket.bucketWebsiteDomainName, {
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
httpPort: 80,
httpsPort: 443
}),
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
compress: true
},
certificate: certificate
});



// Output the CloudFront domain name
new cdk.CfnOutput(this, 'StaticSiteCDNDomain', {
value: cdn.domainName,
});

the complete file should look like be

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { aws_s3 as s3 } from 'aws-cdk-lib';
import { aws_s3_deployment as s3deploy } from 'aws-cdk-lib';
import { aws_certificatemanager as acm } from 'aws-cdk-lib';
import { aws_cloudfront as cloudfront } from 'aws-cdk-lib';
import { aws_cloudfront_origins as origins } from 'aws-cdk-lib';

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

const DOMAIN_NAME = 'site.example.com';

// Create an S3 bucket to host the static website
const bucket = new s3.Bucket(this, 'StaticWebsiteBucket', {
bucketName: DOMAIN_NAME,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
publicReadAccess: true,
blockPublicAccess: new s3.BlockPublicAccess({
blockPublicAcls: false,
blockPublicPolicy: false,
ignorePublicAcls: false,
restrictPublicBuckets: false
}),
versioned: true,
websiteIndexDocument: 'index.html',
});

// Deploy website files to S3 bucket
new s3deploy.BucketDeployment(this, 'DeployWebsite', {
sources: [s3deploy.Source.asset('./website')], // Path to the directory containing the index.html file
destinationBucket: bucket,
});

// Create a reference to SSL certificate in ACM
const certificateArn = 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012';
const certificate = acm.Certificate.fromCertificateArn(this, 'StaticSiteCertificate', certificateArn);

// Create a CloudFront distribution
const cdn = new cloudfront.Distribution(this, 'StaticSiteCDN', {
domainNames: [DOMAIN_NAME],
defaultBehavior: {
origin: new origins.HttpOrigin(bucket.bucketWebsiteDomainName, {
protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY,
httpPort: 80,
httpsPort: 443
}),
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
compress: true
},
certificate: certificate
});

// Output the CloudFront domain name
new cdk.CfnOutput(this, 'StaticSiteCDNDomain', {
value: cdn.domainName,
});

}
}

Step 6: Deploy

At the command line, run the command to deploy the AWS Stack of resources to the cloud

$ cdk deploy

Final Step

Add a CNAME DNS record for the custom domain using the CDN domain name, follow these steps:

Updating DNS Settings at Your Domain Registrar:

  1. Sign in to your domain registrar’s website.
  2. Go to the DNS settings or management section.
  3. Add a DNS Record.
  4. Choose Record Type: CNAME record.
  5. Enter your domain name (e.g., example.com).
  6. Enter CDN Domain: given by your CloudFront (e.g., d12345.cloudfront.net).
  7. Save Changes
  8. Propagation: Allow time for DNS changes to propagate

Once the DNS changes have propagated, your custom domain will be successfully mapped to the CDN domain name, allowing visitors to access your website through your own branded domain.

--

--