How to create multiple Stacks and Environments in a CDK App


Borislav Hadzhiev

Wed Apr 13 20224 min read

Updated - Wed Apr 13 2022

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.

Let's start with the definition of our Stack:

import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as cdk from 'aws-cdk-lib'; // 👇 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 has a deploymentEnvironment property that can have the values of dev or prod.

  2. We set the props parameter in the constructor method of the class to be the MyCdkStackProps interface we just created

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

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

import * as cdk from 'aws-cdk-lib'; 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 use for the scope parameter in 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:

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:

npx aws-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:

npx aws-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:

npx aws-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 #

Use the search field on my Home Page to filter through my more than 1,000 articles.