How to create your own low cost website using CDK (part 2)
Photo by Greg Rakozy / Unsplash
By Ali Tahiri -  3 min read

How to create your own low cost website using CDK (part 2)

Chapter V: Understanding what have been done

Let's go back to this code from part 1:


import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3Deploy from 'aws-cdk-lib/aws-s3-deployment';

class s3Stack extends Stack {
  public readonly webSiteBucket: s3.Bucket;
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    this.webSiteBucket = new s3.Bucket(this, 'myWebSiteBucket', {
      publicReadAccess: true,
      websiteIndexDocument: 'index.html',
    });
    new s3Deploy.BucketDeployment(this, 'myWebsiteBucketDeployement', {
      sources: [s3Deploy.Source.asset('../website-source/public')],
      destinationBucket: this.webSiteBucket,
    });
    new CfnOutput(this, 'Bucket', { value: this.webSiteBucket.bucketName });
    new CfnOutput(this, 'Bucket URL path', {
      value: this.webSiteBucket.bucketWebsiteUrl,
    });
  }
}

export class InfraStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    new s3Stack(this, 'S3Stack');
  }
}
personal-website/Infra/lib/infra-stack.ts

We've created a stack that we've called s3Stack. This stack inherits from the class Stack that represents a single CloudFormation Stack. In our s3Stack constructor we create a new s3 bucket with public read access and we've specified for AWS that this bucket will be used to host a website by indicating the websiteIndexDocument parameter. To specify the sources of our S3 bucket we used BucketDeployment class which is in aws-cdk-lib/aws-s3-deployment module. CfnOutput give us the ability to write some results of our deployment execution to CloudFormation output and also in the output of cdk deploy --all command.

Chapter VI: Adding CloudFront

Let's modify our code now to include a CloudFront distribution and fix policies on our S3 bucket.

What we've done here is to change the permissions on the S3 bucket; disabling publicReadAccess and blocking all public access. We then create a CloudFront Origin Access Identity and give it permission to access objects on our S3 bucket. Finally we create a CloudFront distribution parameters to have as an origin our S3 bucket with restricted HTTP methods to GET HEAD and OPTIONS.


import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3Deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';

// Interface for stack props for our Route 53 config
interface IStackProps extends StackProps {
  subDomain?: string;
  domainName?: string;
}

class staticWebSiteStack extends Stack {
  constructor(scope: Construct, id: string, props: IStackProps) {
    super(scope, id, props);
    new cfConstruct(this, id, props);
  }
}

class cfConstruct extends Construct {
  constructor(scope: Stack, id: string, props: IStackProps) {
    super(scope, id);
    // Create S3 bucket with the right permissions
    const webSiteBucket = new s3.Bucket(this, 'myWebSiteBucket', {
      publicReadAccess: false,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      websiteIndexDocument: 'index.html',
      websiteErrorDocument: '404.html',
    });
    new CfnOutput(this, 'Bucket', { value: webSiteBucket.bucketName });
    // New Cloudfront Origin Access Identity
    const cloudfrontOIA = new cloudfront.OriginAccessIdentity(
      this,
      'cloudfrontOia'
    );
    // Setting up permissions to access S3 bucket from CloudFront
    webSiteBucket.addToResourcePolicy(
      new iam.PolicyStatement({
        actions: ['s3:GetObject'],
        resources: [webSiteBucket.arnForObjects('*')],
        principals: [
          new iam.CanonicalUserPrincipal(
            cloudfrontOIA.cloudFrontOriginAccessIdentityS3CanonicalUserId
          ),
        ],
      })
    );
    // New CloudFront distribution to serve the S3 bucket
    const distribution = new cloudfront.CloudFrontWebDistribution(
      this,
      'siteDist',
      {
        originConfigs: [
          {
            s3OriginSource: {
              s3BucketSource: webSiteBucket,
              originAccessIdentity: cloudfrontOIA,
            },
            behaviors: [
              {
                isDefaultBehavior: true,
                compress: true,
                allowedMethods:
                  cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
              },
            ],
          },
        ],
      }
    );
    new CfnOutput(this, 'DistributionDomainName', {
      value: distribution.distributionDomainName,
    });
    new s3Deploy.BucketDeployment(this, 'myWebsiteBucketDeployement', {
      sources: [s3Deploy.Source.asset('../website-source/public')],
      destinationBucket: webSiteBucket,
      distribution: distribution,
      distributionPaths: ['/*'],
    });
  }
}

export class InfraStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    new staticWebSiteStack(this, 'staticWebsiteStack', {
      env: {
        account: this.node.tryGetContext('accountId'),
        region: this.node.tryGetContext('region'),
      },
    });
  }
}
personal-website/Infra/lib/infra-stack.ts

To deploy this new version of our infrastructue you need to add context variables (accountId and region) to cdk.json file and then execute this command:

cdk deploy --all
Deploy new version of Infra

Chapter VII: Adding our own domain name

You can register a new domain name or migrate your domain name to AWS Route 53. It is pretty straight forward. Once that is done, we can configure Route 53 now to redirect traffic to our CloudFront distribution. We can also create TLS certificate for our website.

Here is the final version of our file

To deploy the final version of our infrastructue you need to add two more context variables (subdomain and domain) to cdk.json file and then execute the same command as always.

Leave a comment by becoming a member
Already have an account? Sign in