AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (2024)

# Table of Contents

  1. Introduction
  2. Creating a new CDK App
  3. File structure
  4. Constructs - introduction
  5. Creating Resources via Constructs
  6. Construct Parameters
  7. Printing diffs of Resources
  8. Listing CDK Stacks
  9. Generating CloudFormation templates with CDK Synth
  10. Deploying our CloudFormation Stack
  11. Updating a CDK Stack
  12. Identifiers in CDK
  13. Adding Outputs to a Stack
  14. Creating a second Stack in our CDK App
  15. Clean up

# Introduction

We're going to create a CDK app and go through a step-by-step explanation of thethings we need to know to feel confident when using CDK to provisioninfrastructure.

The code for this article is available on GitHub

# Creating a new CDK App

Prerequisites - You have to have the CDK CLI installed and the AWS CLI installed and configured.

In order to create a new CDK App we have to use the cdk init command.

We can write our CDK code in many programming languages and init our CDK appfrom multiple starter templates. To list the available options we can append the--list flag to the command.

shell

Copied!

npx aws-cdk init --list

The output shows all the available programming languages we can use in our CDKapp and all of the available starter templates.

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (1)

There are 3 templates we can start from:

  • app - a basic starter template.
  • lib - a template for writing a CDK construct library.
  • sample-app - a starter with some constructs included.

We'll use the app template with the TypeScript language.

Note that cdk init cannot be run in a non-empty directory, so we first have tocreate one.

shell

Copied!

mkdir cdk-appcd cdk-appnpx aws-cdk init app --language=typescript

The output from the command looks as follows.

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (2)

# File structure

At this point, we have an empty CDK project. Let's look at some of the moreimportant files in the project.

In the root directory, we have some configuration files, most of which arelanguage specific.

  • package.json - manages our node packages and scripts
  • jest.config.js - configuration for testing
  • tsconfig.json - TypeScriptconfiguration

We also have a helpful README.md file with the most commonly used CDKcommands.

The first cdk specific file in the root directory iscdk.json and it looks something similar to thefollowing.

cdk.json

Copied!

{ "app": "npx ts-node --prefer-ts-exts bin/cdk-app.ts", "context": { "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, // ...rest }}

The app key tells theCDK CLI how to run ourcode. We're using TypeScript, so our code has to be compiled down to JavaScriptand that's what the ts-node package does.

The command points to the location of our CDK App:

shell

Copied!

npx ts-node --prefer-ts-exts bin/cdk-app.ts

The feature flags in the context object give us the option to enable ordisable some breaking changes that have been made by the AWS CDK team outside ofmajor version releases.

In short, feature flags allow the AWS CDK team to push new features that causebreaking changes without having to wait for a major version release. They canjust enable the new functionality for new projects, whereas old projects withoutthe flags will continue to work.

Next, let's take a look at the entry point of our CDK app in thebin/cdk-app.ts file.

Every CDK App can consist of one or more Stacks. Youcan think of a stack as a unit of deployment.

For instance, we could have one stack for our dev environment and one for ourprod environment, and both can be created in the scope of the same CDK App. Ifyou're familiar with stacks in CloudFormation, it's the same thing.

The code for this article is available on GitHub

Update the CdkAppStack class instantiation in the bin/cdk-app.ts file tolook as follows.

bin/cdk-app.ts

Copied!

import * as cdk from 'aws-cdk-lib';import 'source-map-support/register';import {CdkAppStack} from '../lib/cdk-app-stack';const app = new cdk.App();new CdkAppStack(app, 'CdkAppStack', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});

The env property allows us to specify theAWS environment (account and region) where our stack will be deployed.

The CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION environment variables aremade available in our CDK code and by default resolve to theaccount and region of our default AWS CLIprofile.

# Constructs - introduction

In order to provision resources using CDK we have to define Constructs withinour CDK stack.

Constructs are cloud components. They provideus with:

  • sane defaults, so we don't have to dive deep into CloudFormation docs,unless we need to diverge from the default behavior
  • helper methods that ease the service-to-service interactions, such aspermission grants
  • a very concise and maintainable way to define infrastructure, using higherlevel abstractions (than CloudFormation) and leveraging the power of our IDEby writing in a programming language rather than a configuration language(YAML or JSON)

# Creating Resources via Constructs

Next, let's instantiate our first constructs in the lib/cdk-app-stack.ts file.We'll create an S3 bucket and aDynamoDB table:

lib/cdk-app-stack.ts

Copied!

import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';import * as s3 from 'aws-cdk-lib/aws-s3';import * as cdk from 'aws-cdk-lib';export class CdkAppStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // 👇 use the Bucket construct const bucket = new s3.Bucket(this, 'avatars-bucket', { removalPolicy: cdk.RemovalPolicy.DESTROY, }); // 👇 use the Table construct const table = new dynamodb.Table(this, 'todos-table', { partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY, }); }}

In the code sample we:

  1. Used theBucket constructto define an S3 bucket resource. We set the removalPolicy prop toRemovalPolicy.DESTROY, which means that if the bucket is empty at the timewe delete our stack, it will also get deleted.

  2. Used theTable constructto define a Dynamodb table resource. We set the billingMode prop of thetable to PAY_PER_REQUEST, to avoid incurring any charges, as we won't bemaking any requests. We also set the removalPolicy of the table toREMOVALPolicy.DESTROY, so the table gets deleted when we delete our stack.

By default, the removalPolicy prop of stateful resources (S3 buckets, databases) is set to RETAIN, which means that when we delete our stack, the resources will remain in our account.

# Construct Parameters

Looking at the code in the snippet, we can see a pattern - both the Bucketand Table constructs receive the same 3 parameters:

  • The scope parameter specifies the parent construct within which the childconstruct is initialized. In JavaScript, we use the this keyword, inPython self, etc.

  • The id parameter - an identifier that must be unique within the scope.The combination of CDK identifiers for a resource builds the CloudFormationLogical ID of the resource. I've written an article that explainswhat AWS CDK identifiers are if you want to readmore on the topic.

  • The props parameter - key-value pairs used to set configuration optionsfor the resources that the construct provisions. Note that the props ofdifferent constructs vary.

At this point we have defined 2 resources in our CDK Stack:

  • an S3 bucket
  • a Dynamodb table

Before we move on to provisioning our resources, it's very important to go overhow CDK actually works.

CDK is just a wrapper aroundCloudFormation.CDK enables us to provision infrastructure using a programming language(TypeScript, Python, Java), rather than a configuration language (YAML, JSON).

The whole point of CDK is to improve developer experience, by providing a moremaintainable approach to infrastructure provisioning than CloudFormation.

However, before we run a CDK deployment, our CDK code gets compiled down toCloudFormation code.

After we deploy the CloudFormation code, we can view the stack in theCloudFormation console.

# Printing diffs of Resources

Let's run the cdk diff command to see what changes would occur in case wedeployed our CDK code:

shell

Copied!

npx aws-cdk diff

The output from the command looks as follows:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (3)

We can see that if we were to deploy our CDK stack at this point, we wouldprovision 2 resources:

  • AWS::S3::Bucket
  • AWS::DynamoDB::Table

These are theresource type namesfrom CloudFormation

The cdk diff command compares the deployed and local versions of ourstack. Since we have not deployed our stack yet, it just shows us that if wewere to deploy right now, we'd provision the 2 resources.

It's a very handy command when iterating and updating your infrastructure,because if you make any changes that would delete or update a resource, youwould immediately see the change in the output of cdk diff.

# Listing CDK Stacks

The next command we'll use is cdk list:

shell

Copied!

npx aws-cdk list

The output looks as follows.

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (4)

The cdk list command lists the names of all of the stacks in our CDK App.

The name of our stack is inferred from the id prop, passed when instantiatingthe stack in bin/cdk-app.ts:

bin/cdk-app.ts

Copied!

const app = new cdk.App();// 👇 stack name inferred from herenew CdkAppStack(app, 'CdkAppStack', { env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});

Let's update the name of our CDK stack to cdk-stack-dev.

Update the CdkAppStack instantiation to look as follows:

bin/cdk-app.ts

Copied!

import * as cdk from 'aws-cdk-lib';import 'source-map-support/register';import {CdkAppStack} from '../lib/cdk-app-stack';const app = new cdk.App();new CdkAppStack(app, 'cdk-stack-dev', { stackName: 'cdk-stack-dev', env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});

Let's run the list command again:

shell

Copied!

npx aws-cdk list

The output reflects the change we've made:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (5)

A very handy flag on the CDK list command is the --long flag. It includesinformation about the environment(account, region) of our CDK application:

shell

Copied!

npx aws-cdk list --long

The output looks as follows:

shell

Copied!

- id: cdk-stack-dev name: cdk-stack-dev environment: account: '123456789012' region: us-east-1 name: aws://123456789012/us-east-1

# Generating CloudFormation templates with CDK Synth

Next, we're going to generate and print the CloudFormation equivalent of theCDK stack we've defined.

In other words, we're going to synthesize a CloudFormation template, based onthe stack we've written in lib/cdk-app-stack.ts.

To do that we have to use the synth command.

shell

Copied!

npx aws-cdk synth

After we run the synth command, we can see the CloudFormation equivalent ofour stack.

The synth command did a couple of things:

  • ran our CDK code, so we'd see any syntax or type errors in case we had any.The command knows how to run our code because of the app key in thecdk.json file, located in the root directory of our project
  • generated the CloudFormation template we are going to deploy. The file islocated at cdk.out/cdk-stack-dev.template.json

If you open the cdk.out directory, you should be able to see thecdk-stack-dev.template.json CloudFormation template:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (6)

As expected, the CloudFormation template is equivalent to the stack we'vedefined and provisions both of our resources - the bucket and the table.

# Deploying our CloudFormation Stack

At this point, our template has been generated and stored in the cdk.outdirectory. We're ready to deploy our CloudFormation stack.

Let's run the deploy command:

shell

Copied!

npx aws-cdk deploy

It shouldn't take long before our CloudFormation stack is deployed. You canvisit theCloudFormation console to lookat the details.

If we select cdk-stack-dev and click on Resources, we can see the resourcesour stack has provisioned:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (7)

At this point, we've successfully deployed our CDK stack.

Since CDK is just a wrapper around CloudFormation, the service doesn't have its own console. For a visual representation of a CDK deployment, we use the CloudFormation console.

# Updating a CDK Stack

Let's perform an update. We've decided that the partition key name for ourDynamodb table should be id, instead of todoId.

Let's make the change in lib/cdk-app-stack.ts:

lib/cdk-app-stack.ts

Copied!

const table = new dynamodb.Table(this, 'todos-table', {- partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER},+ partitionKey: {name: 'id', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY,})

Changing the partition key of a DynamoDB table is an update that requiresresource replacement. Our old table will be deleted and a new one with the newpartition key will be created.

Let's run the diff command.

shell

Copied!

npx aws-cdk diff

The output of the command shows that if we were to deploy at this point, thetable resource would get deleted and a new one with the new partition key wouldget created:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (8)

It's a best practice to run the cdk diff command before deploying, especially when working with stateful resources (buckets, databases).

In order to update a CDK stack we run the cdk deploy command.

Running the cdk synth command before deploying is optional. When we runcdk deploy the CDK CLI automatically runs cdk synth before every deployment.

Let's deploy our stack.

shell

Copied!

npx aws-cdk deploy

If we open the Dynamodb console, wecan see that the new table with the partition key of id has been provisionedand the old one has been deleted:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (9)

# Identifiers in CDK

Next, I'll demonstrate a common source of confusion (it was for me), for CDKbeginners.

You don't have to write this code, it's just for demonstration purposes, I'll revert the code after.

I'll make a small change to the second parameter of my DynamoDB table. Don'tmake this change, I'll revert the code after.

Copied!

- const table = new dynamodb.Table(this, 'todos-table', {+ const table = new dynamodb.Table(this, 'table', { partitionKey: {name: 'id', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY,})

I've changed the second parameter I'm passing to theTable construct -the identifier.

I'll now run the diff command.

shell

Copied!

npx aws-cdk diff

The output is:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (10)

We can see that if I were to deploy after changing the id prop of the Tableconstruct, my old DynamoDB table would get deleted and a new one would getcreated.

The reason is that by changing the id prop, I've changed the CloudFormationLogical ID of the table resource. Changing a Logical ID of a resource inCloudFormation deletes the old resource and creates a new resource with the newLogical ID.

We can see the Logical IDs of the resources we've provisioned by opening theCloudFormation console. TheLogical ID is visible in the first column:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (11)

You might be thinking, well then I'm never going to change the id prop I passto constructs, that's an easy fix.

However the CloudFormation logical id is constructed as a combination of theid props of the different scopes (among other things). I've written anotherarticle on this - What is an identifier in AWS CDK,

In short - if we were to extract the code that instantiates the Tableconstruct in another class, we would change the CloudFormation logical ID ofthe table resource. If we then deploy these changes, the old table will getdeleted and a new one with the new logical id will get created.

The reason I've included this part is that when I first started provisioning myinfrastructure using a programming language, I expected that I can refactor mycode (extract classes, etc) in any way I want without any consequences for myinfrastructure, however, that's not the case.

I've reverted the change:

Copied!

- const table = new dynamodb.Table(this, 'table', {+ const table = new dynamodb.Table(this, 'todos-table', { partitionKey: {name: 'id', type: dynamodb.AttributeType.NUMBER}, billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, removalPolicy: cdk.RemovalPolicy.DESTROY,})

# Adding Outputs to a Stack

Let's add Outputs to our CDK stack. Outputs are values that we can import intoother stacks, or in our case redirect to a file on the local file system.

It's a very common scenario that resource identifiers (i.e. bucket name, API URL) have to be kept in sync between our backend and frontend code.

By redirecting the outputs to a json file on the file system, we enable ourfrontend code to import the properties and use them.

Let's add Outputs to our stack in ourlib/cdk-app-stack.ts:

lib/cdk-app-stack.ts

Copied!

import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';import * as s3 from 'aws-cdk-lib/aws-s3';import * as cdk from 'aws-cdk-lib';export class CdkAppStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { super(scope, id, props); // ... rest new cdk.CfnOutput(this, 'bucketName', { value: bucket.bucketName, }); new cdk.CfnOutput(this, 'tableName', {value: table.tableName}); }}

We used theCfnOutputconstruct to create an output for the names of our bucket and table.

Let's run the diff command:

shell

Copied!

npx aws-cdk diff

We can see that if we were to deploy at this point, 2 Output values would getcreated:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (12)

This time we'll add a new flag to the cdk deploy command. The --outputs-fileflag allows us to write the outputs we've defined in our stack to a file on thelocal filesystem.

shell

Copied!

npx aws-cdk deploy --outputs-file ./cdk-outputs.json

Let's take a look at the cdk-outputs.json file in the root directory of ourproject:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (13)

We can see that the bucket and table names have been written to the JSON file.Now our frontend (if we had one), would be able to import the file and use anyof the values, such as an API URL.

# Creating a second Stack in our CDK App

The next thing we're going to do is create a second stack in our CDK app.

We have to instantiate the second stack in our bin/cdk-app.ts file:

bin/cdk-app.ts

Copied!

import * as cdk from 'aws-cdk-lib';import 'source-map-support/register';import {CdkAppStack} from '../lib/cdk-app-stack';const app = new cdk.App();new CdkAppStack(app, 'cdk-stack-dev', { stackName: 'cdk-stack-dev', env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});new CdkAppStack(app, 'cdk-stack-prod', { stackName: 'cdk-stack-prod', env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, },});

Let's run the diff command:

shell

Copied!

npx aws-cdk diff cdk-stack-dev cdk-stack-prod

The output shows that there aren't any differences in our cdk-stack-dev.

However, if we deploy the cdk-stack-prod, the Bucket and Table resources wouldbe created:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (14)

Let's synth the stacks to generate the CloudFormation templates:

shell

Copied!

npx aws-cdk synth cdk-stack-dev cdk-stack-prod

Now if we take a look at the cdk.out directory, we can see that we'vegenerated 2 CloudFormation templates. One for our cdk-stack-dev and one forcdk-stack-prod:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (15)

In most real-world applications, you're going to have to manage more than onestack.

A couple of reasons are:

  • you don't want to write to your production database while developing yourapplication
  • usually, you provision resources with lower capacity for your developmentenvironment, i.e. an EC2 instance typet3.micro instead of m5n.xlarge.

Let's deploy our stacks. We haven't made any changes to the cdk-stack-dev, butwe'll deploy it anyway, to demo the syntax:

shell

Copied!

npx aws-cdk deploy cdk-stack-dev cdk-stack-prod

At this point we have both of our stacks deployed. If we open the CloudFormationconsole, we can see that both of our stacks provision a separate bucket andtable:

AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (16)

To only deploy, synth, or diff a specific stack we just have to specifythe name in the command. For example, to only deploy the dev stack:

shell

Copied!

npx aws-cdk deploy cdk-stack-dev

# Clean up

In order to delete the stacks we've provisioned, we have to use the destroycommand:

shell

Copied!

npx aws-cdk destroy cdk-stack-dev cdk-stack-prod

# Additional Resources

You can learn more about the related topics by checking out the followingtutorials:

  • What is a Token in AWS CDK
  • What is an identifier in CDK
  • How does AWS CDK work
  • CDK Constructs - Complete Guide
  • How to use Context in AWS CDK
  • How to use Parameters in AWS CDK
  • You must Specify a Region Error in AWS CLI [Solved]
AWS CDK Tutorial for Beginners - Step-by-Step Guide | bobbyhadz (2024)

References

Top Articles
Latest Posts
Article information

Author: Margart Wisoky

Last Updated:

Views: 5791

Rating: 4.8 / 5 (58 voted)

Reviews: 89% of readers found this page helpful

Author information

Name: Margart Wisoky

Birthday: 1993-05-13

Address: 2113 Abernathy Knoll, New Tamerafurt, CT 66893-2169

Phone: +25815234346805

Job: Central Developer

Hobby: Machining, Pottery, Rafting, Cosplaying, Jogging, Taekwondo, Scouting

Introduction: My name is Margart Wisoky, I am a gorgeous, shiny, successful, beautiful, adventurous, excited, pleasant person who loves writing and wants to share my knowledge and understanding with you.