Provision a static website with AWS Cloud Development Kit (CDK)

Curtis Hughes
6 min readDec 24, 2020

--

In a previous article I wrote about the architecture, resources, and manual process needed for Hosting a single page application with S3 and CloudFront. This article will focus on automating that process using Amazon’s Cloud Development Kit (CDK).

If you are unfamiliar with CDK check out the official amazon page for a complete detail of the product and benefits. Simply put, it is a software development kit for provisioning AWS resources via infrastructure as code (IaC). The kit is available in multiple languages, but this article will focus on the Typescript variant.

Prerequisites

  • Node 10.3.0 or later
  • AWS account (and a basic understanding of the platform)

Create an IAM User

It is always good practice to create a least privileged user when programmatically interacting with AWS. Head over to the AWS Console and navigate to IAM. Add a new user with programatic access. Here is an example policy you can use for the automation outlined in the first part of this article (NOTE: This policy still has a wide range of access within your AWS account so use it with caution):

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "cloudformation:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "cloudfront:*",
"Resource": "*"
}
]
}

Take note of the AccessKeyId and SecretAccessKey, you will need these later when executing the cdk cli commands (IMPORTANT: Do NOT check these into version control).

Initialize the CDK project

The following command will initialize a cdk project (with Typescript) in the current working directory:

# inside a new directory named cdk-example
$ npx aws-cdk init app --language typescript

NOTE: I use the npm included npx command here to avoid installing the dependency globally.

You should now have a project structure that looks something like this:

boilerplate file structure for cdk application

If you look at the cdk.json file you will see that it outlines how the cdk cli should run the application (i.e. `“app”: “npx ts-node bin/cdk-example.ts”`) and some context for the cli to consider when executing:

// cdk.json
{
"app": "npx ts-node bin/cdk-example.ts",
"context": {
"@aws-cdk/core:enableStackNameDuplicates": "true",
"aws-cdk:enableDiffNoFail": "true",
"@aws-cdk/core:stackRelativeExports": "true",
"@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true
}
}

Open up bin/cdk-example.ts to see the main executable code for the application.

// bin/cdk-example.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { CdkExampleStack } from '../lib/cdk-example-stack';
const app = new cdk.App();
new CdkExampleStack(app, 'CdkExampleStack', {
env: {
region: 'us-east-1',
}
});

The above code will initialize a new cdk application and create a CloudFormation stack (with the name “CdkExampleStack”) based on the constructs outlined in the `CdkExampleStack` class:

// lib/cdk-example-stack.ts
import * as cdk from '@aws-cdk/core';
export class CdkExampleStack extends cdk.Stack {
constructor(
scope: cdk.Construct,
id: string,
props?: cdk.StackProps
) {
super(scope, id, props);
}
}

You can preview the CloudFormation template that cdk generates for the application with the `synth` command:

$ yarn cdk synth

Notice the current stack only creates a single resource for defining the cdk metadata. We can deploy the application right now using the `deploy` command (and the IAM user keys we saved in the previous step):

$ AWS_ACCESS_KEY_ID=********* AWS_SECRET_ACCESS_KEY=********* yarn cdk deploy

Easy enough right? You can verify the deployment by navigating to the AWS console and searching for the created stack within CloudFormation.

Provision the stack

Now that we understand the basic functionality of cdk let’s modify our application to provision the resources for a static website. Create a new file within the `lib` directory named `StaticWebsite.ts`. This is where we will define the resources and configuration for our static website.

```ts
// lib/StaticWebsite.ts
import { Stack, StackProps, App, RemovalPolicy } from '@aws-cdk/core';
import { Bucket, BlockPublicAccess } from '@aws-cdk/aws-s3';
import { OriginAccessIdentity, CloudFrontWebDistribution } from '@aws-cdk/aws-cloudfront';
export class StaticWebsite extends Stack {
constructor(scope: App, id: string, props: StackProps) {
super(scope, id, props);
const oai = new OriginAccessIdentity(
this,
`CloudFrontOriginAccessIdentity`
);
const bucket = new Bucket(this, 'S3Bucket', {
publicReadAccess: false,
blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
removalPolicy: RemovalPolicy.DESTROY,
});
bucket.grantRead(oai);
new CloudFrontWebDistribution(
this,
'CloudFrontDistribution',
{
originConfigs: [
{
s3OriginSource: {
s3BucketSource: bucket,
originAccessIdentity: oai,
},
behaviors: [{ isDefaultBehavior: true }],
},
],
errorConfigurations: [
{
errorCode: 404,
responseCode: 200,
responsePagePath: '/index.html',
},
{
errorCode: 403,
responseCode: 200,
responsePagePath: '/index.html',
},
],
}
);
}
}

Notice we added two new dependencies (`@aws-cdk/aws-s3` & `@aws-cdk/aws-cloudfront`). These come from the official AWS module library. You can install them with the following command (or with npm if you prefer):

$ yarn add @aws-cdk/aws-s3 @aws-cdk/aws-cloudfront

Now let’s add the StaticWebsite stack to our application. Open up the `bin/cdk-example.ts` file and modify it like so:

// bin/cdk-example.ts
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { StaticWebsite } from '../lib/StaticWebsite';
const app = new cdk.App();
new StaticWebsite(app, 'CdkExampleStack', {
env: {
region: 'us-east-1',
}
});

If we run the `diff` command with the above changes we can see the changes that cdk will make on our next deployment (again remembering to use our IAM user keys that we created earlier):

$ AWS_ACCESS_KEY_ID=********* AWS_SECRET_ACCESS_KEY=********* yarn cdk diff

From the image, you can see that our changes will add four new resources (ie. S3 Bucket, S3 Bucket Policy, CloudFront Distribution, CloudFront OAI). We can run the `deploy` command again to provision the changes:

$ AWS_ACCESS_KEY_ID=********* AWS_SECRET_ACCESS_KEY=********* yarn cdk deploy

We have now successfully provisioned the necessary resources for a static website in AWS. You can view the updated CloudFormation stack resources from the console.

The next step will involve uploading your build artifacts to the stack’s S3 bucket.

Upload build artifacts

In this section I will outline a few potential ways to handle uploading the build artifacts (i.e. html, js, css, etc) for your static website. This is certainly not a comprehensive list and is just meant to provide some available options:

  • AWS CDK: Yep, it can handle this too! The AWS module library includes a BucketDeployment construct designed to handle syncing local directories to S3. Just add the object to the existing StaticWebsite stack and pass in the require parameters. This can be a quick and easy way to handle deployments, but in my opinion adds some unnecessary resources (ie. An AWS Lambda function, and an IAM role).
  • AWS CLI: The AWS CLI provides a nice sync method specifically designed to upload files to S3. This is probably my preferred way to handle static website deployments because it doesn’t add any additional infrastructure resources and can still be automated.
  • AWS Console: If you are just working on a personal project or want to expedite the deployment process, you can certainly just manually upload the artifacts to the S3 bucket.

Clean up (Optional)

Another major benefit of cdk is that you can destroy infrastructure as quickly as you create it. Once you are finished with your website you can run the `destroy` command to delete the stack and all of its resources (NOTE: some resources have configuration that allow them to remain even if the stack is deleted):

$ AWS_ACCESS_KEY_ID=********* AWS_SECRET_ACCESS_KEY=********* yarn cdk destroy

Wrapping up

Thanks for reading! In this article I used a couple of the core commands available from the cdk cli (synth, diff, deploy, destroy). You can view the entire list of commands from the official AWS CDK Toolkit guide. You can also view some of the example projects provided by AWS for common infrastructure setups at https://github.com/aws-samples/aws-cdk-examples.

--

--