
Deploy a Static Website on S3 with a Custom Domain Using AWS CDK
In this blog post, we'll illustrate how CDK can be utilized to provision a static s3 web site. We'll then deploy this setup with a custom domain, leveraging Certificate Manager and your chosen DNS registrar, thereby circumventing Route 53 configuration and associated charges.
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. We’ll use it’s reference ARN later.
Requesting a Certificate in AWS Console:
- Sign in to the AWS Management Console and access your account.
- Open AWS Certificate Manager (ACM)
- Request a Certificate by clicking on the “Request” button.
- Enter Domain Name and click “Request”.
- Open Certificate from the list, and Store CNAME Details: Note the CNAME name and values provided.
- Adding a DNS Record:
- Log in to your domain registrar’s website.
- Go to the DNS settings or management section.
- Add a CNAME record: Use the AWS-provided name in the “Name” or “Host” field and the CNAME value in the “Value” field.
- Save the changes.
Awaiting Validation:
- Return to the AWS ACM console.
- Refresh the page to check the validation status of your certificate. It might take a while.
- Once validated and the certificate status changes to “Issued,” you are not ready to use the certificate.
Create your CDK Project
1$ mkdir s3-static-website 2$ cd s3-static-website 3$ 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
1import * as cdk from 'aws-cdk-lib'; 2import { Construct } from 'constructs'; 3 4export class S3StaticWebsiteStack extends cdk.Stack { 5 constructor(scope: Construct, id: string, props?: cdk.StackProps) { 6 super(scope, id, props); 7 8 } 9}
Step 1: Provision a Bucket
Create a bucket to be destroyed when we delete the stack, and make the bucket publicly accessible
1import * as cdk from 'aws-cdk-lib'; 2import { Construct } from 'constructs'; 3import { aws_s3 as s3 } from 'aws-cdk-lib'; 4 5export class S3StaticWebsiteStack extends cdk.Stack { 6 constructor(scope: Construct, id: string, props?: cdk.StackProps) { 7 super(scope, id, props); 8 9 const DOMAIN_NAME = 'example.com'; 10 11 // Create an S3 bucket to host the static website 12 const bucket = new s3.Bucket(this, 'StaticWebsiteBucket', { 13 bucketName: DOMAIN_NAME, 14 removalPolicy: cdk.RemovalPolicy.DESTROY, 15 autoDeleteObjects: true, 16 publicReadAccess: true, 17 blockPublicAccess: new s3.BlockPublicAccess({ 18 blockPublicAcls: false, 19 blockPublicPolicy: false, 20 ignorePublicAcls: false, 21 restrictPublicBuckets: false 22 }), 23 versioned: true, 24 websiteIndexDocument: 'index.html', 25 }); 26 27 28 } 29}
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
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" /> 7 <title>Document</title> 8</head> 9<body> 10 My Static Website 11</body> 12</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.
1import { aws_s3_deployment as s3deploy } from 'aws-cdk-lib'; 2 3... 4 5// Deploy website files to S3 bucket 6new s3deploy.BucketDeployment(this, 'DeployWebsite', { 7 sources: [s3deploy.Source.asset('./website')], // Path to the directory containing the index.html file 8 destinationBucket: bucket, 9}); 10
Step 4. Create a Reference to the SSL certificate
1import { aws_certificatemanager as acm } from 'aws-cdk-lib'; 2 3... 4 5// Create a reference to SSL certificate in ACM 6const certificateArn = 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012'; 7const certificate = acm.Certificate.fromCertificateArn(this, 'StaticSiteCertificate', certificateArn); 8
Step 5: Create the CloudFront CDN
1import { aws_cloudfront as cloudfront } from 'aws-cdk-lib'; 2import { aws_cloudfront_origins as origins } from 'aws-cdk-lib'; 3 4... 5 6// Create a CloudFront distribution 7const cdn = new cloudfront.Distribution(this, 'StaticSiteCDN', { 8 domainNames: [DOMAIN_NAME], 9 defaultBehavior: { 10 origin: new origins.HttpOrigin(bucket.bucketWebsiteDomainName, { 11 protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, 12 httpPort: 80, 13 httpsPort: 443 14 }), 15 allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD, 16 compress: true 17 }, 18 certificate: certificate 19}); 20 21 22 23// Output the CloudFront domain name 24new cdk.CfnOutput(this, 'StaticSiteCDNDomain', { 25 value: cdn.domainName, 26}); 27
the complete file should look like be
1import * as cdk from 'aws-cdk-lib'; 2import { Construct } from 'constructs'; 3import { aws_s3 as s3 } from 'aws-cdk-lib'; 4import { aws_s3_deployment as s3deploy } from 'aws-cdk-lib'; 5import { aws_certificatemanager as acm } from 'aws-cdk-lib'; 6import { aws_cloudfront as cloudfront } from 'aws-cdk-lib'; 7import { aws_cloudfront_origins as origins } from 'aws-cdk-lib'; 8 9export class S3StaticWebsiteStack extends cdk.Stack { 10 constructor(scope: Construct, id: string, props?: cdk.StackProps) { 11 super(scope, id, props); 12 13 const DOMAIN_NAME = 'site.example.com'; 14 15 // Create an S3 bucket to host the static website 16 const bucket = new s3.Bucket(this, 'StaticWebsiteBucket', { 17 bucketName: DOMAIN_NAME, 18 removalPolicy: cdk.RemovalPolicy.DESTROY, 19 autoDeleteObjects: true, 20 publicReadAccess: true, 21 blockPublicAccess: new s3.BlockPublicAccess({ 22 blockPublicAcls: false, 23 blockPublicPolicy: false, 24 ignorePublicAcls: false, 25 restrictPublicBuckets: false 26 }), 27 versioned: true, 28 websiteIndexDocument: 'index.html', 29 }); 30 31 // Deploy website files to S3 bucket 32 new s3deploy.BucketDeployment(this, 'DeployWebsite', { 33 sources: [s3deploy.Source.asset('./website')], // Path to the directory containing the index.html file 34 destinationBucket: bucket, 35 }); 36 37 // Create a reference to SSL certificate in ACM 38 const certificateArn = 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012'; 39 const certificate = acm.Certificate.fromCertificateArn(this, 'StaticSiteCertificate', certificateArn); 40 41 // Create a CloudFront distribution 42 const cdn = new cloudfront.Distribution(this, 'StaticSiteCDN', { 43 domainNames: [DOMAIN_NAME], 44 defaultBehavior: { 45 origin: new origins.HttpOrigin(bucket.bucketWebsiteDomainName, { 46 protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, 47 httpPort: 80, 48 httpsPort: 443 49 }), 50 allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD, 51 compress: true 52 }, 53 certificate: certificate 54 }); 55 56 // Output the CloudFront domain name 57 new cdk.CfnOutput(this, 'StaticSiteCDNDomain', { 58 value: cdn.domainName, 59 }); 60 61 } 62}
Step 6: Deploy
At the command line, run the command to deploy the AWS Stack of resources to the cloud
1$ 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:
- Sign in to your domain registrar’s website.
- Go to the DNS settings or management section.
- Add a DNS Record.
- Choose Record Type: CNAME record.
- Enter your domain name (e.g., example.com).
- Enter CDN Domain: given by your CloudFront (e.g., d12345.cloudfront.net).
- Save Changes
- 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.