How to create multiple Stacks and Environments in a CDK App

avatar

Borislav Hadzhiev

Fri Apr 23 20214 min read

Updated on Fri Apr 23 2021

Creating Multiple stacks and environments in a CDK App #

A CDK App can consist of one or more Stacks. A stack in CDK is a 1x1 mapping to a CloudFormation stack, in other words it's a unit of an application deployment.

When we provision infrastructure for real world applications we often have to manage more than 1 stack, for example a dev, prod and a staging stack.

There are many reasons that force us to manage more than 1 stack in our CDK applications, for example:

  • We wouldn't want to write to our production database in development
  • The provisioned capacity of resources differs between environments, for instance we provision a smaller EC2 instance for development and a larger, more expensive one for production
  • Other parameters will differ, for example API keys for external services, etc.

We are going to provision a small CDK app, that consists of 2 stacks - dev and prod.

The code for this article is available on GitHub

For our dev stack we are going to provision a Dynamodb table with provisioned read and write capacity of 1 unit.

For our prod stack we are going to provision a Dynamodb table with capacity set to on Demand.

We'll start with the definition of our Stack:

lib/cdk-starter-stack.ts
import * as dynamodb from '@aws-cdk/aws-dynamodb'; import * as cdk from '@aws-cdk/core'; // ๐Ÿ‘‡ extend the StackProps interface interface MyCdkStackProps extends cdk.StackProps { deploymentEnvironment: 'dev' | 'prod'; } export class MyCdkStack extends cdk.Stack { constructor(scope: cdk.App, id: string, props: MyCdkStackProps) { super(scope, id, props); // ๐Ÿ‘‡ get the environment from props const {deploymentEnvironment} = props; const isProduction = deploymentEnvironment === 'prod'; // ๐Ÿ‘‡ conditionally set capacity based on environment new dynamodb.Table(this, 'my-table', { partitionKey: {name: 'todoId', type: dynamodb.AttributeType.NUMBER}, billingMode: isProduction ? dynamodb.BillingMode.PAY_PER_REQUEST : dynamodb.BillingMode.PROVISIONED, writeCapacity: isProduction ? undefined : 1, readCapacity: isProduction ? undefined : 1, removalPolicy: cdk.RemovalPolicy.DESTROY, }); } }

In the code snippet:

  1. We define an interface for the props, that the stack takes when instantiated. The interface specifies a deploymentEnvironment type, that can have the values of dev or prod.

  2. In our construct we set the props parameter in our constructor method to be the MyCdkStackProps interface we just created

  3. We then take the deploymentEnvironment value from the props object and use it to conditionally set the write and read capacity on the dynamodb table.

Next, we'll instantiate the dev and prod stacks:

bin/cdk-starter.ts
import * as cdk from '@aws-cdk/core'; import {MyCdkStack} from '../lib/cdk-starter-stack'; const app = new cdk.App(); // ๐Ÿ‘‡ instantiate dev stack new MyCdkStack(app, 'my-stack-dev', { stackName: 'my-stack-dev', env: { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT, }, deploymentEnvironment: 'dev', }); // ๐Ÿ‘‡ instantiate prod stack new MyCdkStack(app, 'my-stack-prod', { stackName: 'my-stack-prod', env: { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT, }, deploymentEnvironment: 'prod', });

In the code snippet above:

  1. We initialize a new CDK app, which we'll pass in the scope parameter for our cdk Stacks

  2. We instantiate the dev stack, passing in the deploymentEnvironment prop set to dev.

  3. We instantiate the prod stack, passing in the deploymentEnvironment prop set to prod

Notice that we also set the env property of the stack.

In this case we use the values of the environment variables:

  • CDK_DEFAULT_REGION
  • CDK_DEFAULT_ACCOUNT

These environment variables are made available to us in the CDK environment and correspond to the region and account id of our default AWS profile.

In general, when instantiating CDK stacks, it's recommended to pass in fixed values for the env property, because if you work on a team with multiple engineers, they could have different account and region properties configured in their default AWS CLI profile.

Deploying multiple stacks in CDK #

First we have to synthesize our stacks. The cdk synth commands runs our CDK code and generates the corresponding CloudFormation template in the cdk.out directory of our project.

Let's run the synth command and look at the results:

shell
npx cdk synth \ my-stack-dev \ my-stack-prod

We explicitly specify the names of the stacks we want to synthesize. We set the names, by passing in the stackName prop when we instantiated the Stacks.

synth result

The output from the synth command tells us that the templates have been placed in the cdk.out directory, let's take a look:

cdk out directory

We can see that the CloudFormation templates for both of our stacks have been generated and are ready for deployment.

Next we'll run the cdk deploy command to deploy the generated CloudFormation stacks in our AWS account:

shell
npx cdk deploy \ my-stack-dev \ my-stack-prod

After issuing the command the CDK CLI will create a changeset for both of our CloudFormation templates and then deploy them.

Let's take a look at the result in our CloudFormation console.

This is our dev stack:

dev stack

And this is our prod stack:

prod stack

Notice that the Physical IDs of the Dynamodb tables are different.

If we look at the provisioned Dynamodb tables, we can see that we were able to conditionally set the Capacity mode based on the environment.

The dev table's Capacity is set to Provisioned:

dev table

And the prod table's Capacity mode is set to On-demand:

prod table

Clean up #

To delete the stacks we've provisioned we can run the cdk destroy command:

shell
npx cdk destroy \ my-stack-dev \ my-stack-prod

The CDK CLI will then issue a delete command for both of our CloudFormation stacks:

destroy stacks

Summary #

CDK Applications can consist of one or more CDK stacks.

For most real world applications we need to manage multiple stacks, based on the environment the stack is intended to be run in.

With CDK, provisioning multiple stacks is as simple as instantiating our Stack construct multiple times and passing it different props.

We are then able to conditionally configure / provision our AWS resources based on the environment of the Stack.

Further Reading #

Add me on LinkedIn

I'm a Web Developer with TypeScript, React.js, Node.js and AWS experience.

Let's connect on LinkedIn

Join my newsletter

I'll send you 1 email a week with links to all of the articles I've written that week

Buy Me A Coffee