← Back to list

AWS CDK v2: Enhanced Context Management

Introduction

Lior Dux in Towards Dev · 2025-03-23 11:45 · 2 claps · 8.1 min read
#aws-cdk #context #devops #yaml #json
Open on Medium ↗
Wiki topics: BIZ · Business Strategy ☁️ · DevOps & Cloud

AWS CDK v2: Enhanced Context Management

CDK — Cloud development kit

CDK — Cloud development kit

Introduction

Recently I’ve started using AWS CDK (cloud development kit) to provision my cloud resources in a programmatic fashion. I’ve had my time with Terraform, Terragrunt and CloudFormation, the aws cdk is definitely a different beast. The greatest benefit I got from using it, was how easy it was to collab with other teams, whether they are developers, qa, or devops. If you have a programming background, and some understanding of AWS infrastructure, services and component — you’ll get along just fine.

The great power of CDK lies within collaborations: everyone “speaks” the same language.

Our team uses the typescript aws cdk, so thing may vary if you’re using a different programming language.

What is a context ?

AWS CDK uses a file named cdk.json or cdk.context.json , structured as a key-value store in the json format, to hold different kinds of information:

  • Cached information retrieved from your AWS environment
  • Feature-flags for different libraries

When is a context generated ?

The context file is first generated when we initialize a new aws cdk app. We can use the following command to create a new cdk application named “aws-cdk-context-demo”, with the programming language set to Typescript.

echo "Creating a directory for CDK Application..."
mkdir aws-cdk-context-demo && pushd aws-cdk-context-demo

echo "Initializing the aws-cdk-contenxt-demo CDK Application..."
cdk init --app aws-cdk-contenxt-demo --language=typescript

A new structure has been created for us, with the initial context file:

.
├── README.md
├── bin
├── cdk.json
├── jest.config.js
├── lib
├── node_modules
├── package-lock.json
├── package.json
├── test
└── tsconfig.json

5 directories, 6 files

The initial context contain feature flags for different libraries, and a single array value:

{
  "app": "npx ts-node --prefer-ts-exts bin/aws-cdk-context-demo.ts",
  "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "**/*.js",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
  "context": {
    "@aws-cdk/aws-lambda:recognizeLayerVersion": true,
    "@aws-cdk/core:checkSecretUsage": true,
    "@aws-cdk/core:target-partitions": [
      "aws",
      "aws-cn"
    ],
    "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
    "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
    "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
    "@aws-cdk/aws-iam:minimizePolicies": true,
    "@aws-cdk/core:validateSnapshotRemovalPolicy": true,
    "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
    "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
    "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
    "@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
    "@aws-cdk/core:enablePartitionLiterals": true,
    "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
    "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
    "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
    "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
    "@aws-cdk/aws-route53-patters:useCertificate": true,
    "@aws-cdk/customresources:installLatestAwsSdkDefault": false,
    "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
    "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
    "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
    "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
    "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
    "@aws-cdk/aws-redshift:columnId": true,
    "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
    "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
    "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
    "@aws-cdk/aws-kms:aliasNameRef": true,
    "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
    "@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
    "@aws-cdk/aws-efs:denyAnonymousAccess": true,
    "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
    "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
    "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
    "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
    "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
    "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
    "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
    "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
    "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
    "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
    "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
    "@aws-cdk/aws-eks:nodegroupNameAttribute": true,
    "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
    "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
    "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
    "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
    "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
    "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
    "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
    "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
    "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
    "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
    "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
    "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
    "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
    "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
    "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
    "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
    "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true
  }
}

Bootstrap our AWS environment

Bootstrapping prepares your AWS environment by provisioning specific AWS resources in your environment that are used by the AWS CDK. These resources are commonly referred to as your bootstrap resources. They include the following:

  • Amazon Simple Storage Service (Amazon S3) bucket — Used to store your CDK project files, such as AWS Lambda function code and assets.
  • Amazon Elastic Container Registry (Amazon ECR) repository — Used primarily to store Docker images.
  • AWS Identity and Access Management (IAM) roles — Configured to grant permissions needed by the AWS CDK to perform deployments. For more information about the IAM roles created during bootstrapping, see IAM roles created during bootstrapping.

Let’s bootstrap our environment:

echo "Setting up AWS Account & Region..."
set CDK_DEFAULT_ACCOUNT='1234567890'
set CDK_DEFAULT_REGION='us-east-1'

echo "Bootstraping environment..."
cdk bootsrap 

How to use a context ?

// bin.ts
#!/usr/bin/env node
import { App } from "aws-cdk-lib";
import { AwsCdkContextDemoStack } from "../lib/aws-cdk-context-demo-stack";

const app = new App();

// Consume a context parameter and print it
const env = app.node.tryGetContext("env");
console.log(`env: ${env}`);

new AwsCdkContextDemoStack(app, `${env}-AwsCdkContextDemoStack`, {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION,
  },
});

Setting custom context values can be done in a few way:

1 — Plain and simple, works for a small scale:

Pass different dictionary-structured values by using the --context key=value --context another-key=another-value flag in your aws cdk calls.

cdk synth --context env=dev --context objective=demo

The downsides:

  • On-boarding: knowledge with the project is essential, as the structure (whether that’s a construct or a stack) is dependent on them. Unless, of course, you’ve set default values all around.
  • Repetitiveness: you would have to provide all of these flags for each cdk call (e.g synth / deploy / destroy / diff) ,
  • Error prone: it is easy to perform a typo, miss a space or forget a quote.
  • Scalability issues: as time goes by and projects grow, assuming you’ve maintained flexibility, there's a need for more values, this makes each command long and messy, which also decreases readability significantly.

2 — Easier to maintain, gets messy over time:

Take advantage of the context files provided by the cdk, add your own dictionaries under the context field.

# cdk.context.json / cdk.json
{
  "app": "npx ts-node --prefer-ts-exts bin/aws-cdk-context-demo.ts",
  "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "**/*.js",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
  "context": {
    # A single pair
    "env": "dev",
    # All of a given environment values
    "dev": {
      "objective": "demo",
      "hello": "world!"
    },
    # Omitted...
  }
}
// bin.ts
#!/usr/bin/env node
import { App } from "aws-cdk-lib";
import { AwsCdkContextDemoStack } from "../lib/aws-cdk-context-demo-stack";

const app = new App();

// Consume a context parameter and print it
const env = app.node.tryGetContext("env");
console.log(`env: ${env}`);

// Consume environment context parameters and pass them to the stack
const envContext = app.node.tryGetContext(env);
console.log(`envContext: ${JSON.stringify(envContext)}`);

// Consume specific environment context values
const hello = envContext.hello;
console.log(`hello: ${hello}`);

new AwsCdkContextDemoStack(app, `${env}-AwsCdkContextDemoStack`, {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION,
  },
  // Pass the context here
  // envContext: envContext,
});

The downsides:

  • Readability: the context is written in json, no comment can be added.
  • Maintainability: as you expand into more and more environments, this single file gest long and harder to maintain.
  • Repetitiveness: different environments may be consuming one or more of the same value, we can’t use variables in json.

level 2

level 2

The DevOps way

3 — De-coupled, use interfaces:

Manage each environment’s context in a separate JSON file, and use a single context key to provide the reference to find it. Combine this with custom interfaces for your constructs / stacks to achieve type validation and better error handling.

// bin.ts
#!/usr/bin/env node
import * as fs from "fs";
import * as path from "path";
import { App, Tags } from "aws-cdk-lib";
import {
  AwsCdkContextDemoStack,
  AwsCdkContextDemoStackProps,
} from "../lib/aws-cdk-context-demo-stack";

const app = new App();

// Load context dynamically based on environment
const env = app.node.tryGetContext("env") || "dev";

// Parse reference to context file
let contextFilePath = app.node.tryGetContext(env);
if (contextFilePath.startsWith("@")) {
  contextFilePath = path.join(process.cwd(), contextFilePath.substring(1));
  console.log(`contextFilePath: ${contextFilePath}`);
} else {
  throw new Error(
    "Context file path must be relative to the current working directory, and be referenced with @",
  );
}

// Check if the context file exists
if (!fs.existsSync(contextFilePath)) {
  throw new Error(`Context file for environment '${env}' not found`);
}

// Extract context from the file, and pass it to the construct
const context = JSON.parse(
  fs.readFileSync(contextFilePath, "utf8"),
) as AwsCdkContextDemoStackProps;
console.log(`EnvProps: ${JSON.stringify(context, null, 2)}`);

// Pass context to the construct
new AwsCdkContextDemoStack(app, `${env}-AwsCdkContextDemoStack`, {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION,
  },
  ...context, // Expand the context
});

Start off by creating a configs directory, which would hold each and every environment’s related values. Then, we add JSON library capabilities. This library would take care of parsing the reference and retrieving the appropriate values. We would assign those values to our interface to leverage TypeScript’s type enforcement and validation.

# configs/dev.context.json
{
  "objective": "demo",
  "hello": "world!"
}
# cdk.context.json
{
  "app": "npx ts-node --prefer-ts-exts bin/cdk-app.ts",
  "watch": {
  # Ommitted... #
  "context": {
    "dev": "@configs/dev.context.json", # <<<---- Here is our reference
  }
}

We’re all set, we can now invoke the cdk with a single context key-value pair, and all of our matching environment manifest values would be loaded. like so:

cdk deploy --context env=dev

JSON vs. YAML

JSON vs. YAML

4 — Use YAML, maintain standards:

The YAML Language offers a variety of different features compared to a simple JSON, like anchors, aliases, timestamps, and much more. You can add comments and references where needed.

This time we would “upgrade” our usage by creating a utils.ts library with a function to do the work for us, as long as we make sure to follow the standards for naming and config files placements configs/${env}.context.json . This way we do NOT modify the original cdk.json or cdk.context.json at all.

// utils.ts
#!/usr/bin/env node
import * as fs from "fs";
import * as path from "path";
import * as yaml from "js-yaml";
import { AwsCdkContextDemoStackProps } from "../lib/aws-cdk-context-demo-stack";

export function loadConfig(env: string): AwsCdkContextDemoStackProps {
  // `../` point to the cdk appplication root.
  const contextFilePath = path.join(
    __dirname,
    `../configs/${env}.context.yaml`,
  );
  console.log(`contextFilePath: ${contextFilePath}`);
  if (!fs.existsSync(contextFilePath)) {
    throw new Error(`Context file not found`);
  }

  const fullContext = yaml.load(
    fs.readFileSync(contextFilePath, "utf-8"),
  ) as AwsCdkContextDemoStackProps;
  const context: AwsCdkContextDemoStackProps = fullContext;

  console.log("\n##############################################");
  console.log(`#                EnvProps:                   #`);
  console.log("##############################################");
  console.log(`${yaml.dump(context, { indent: 2 })}`);
  console.log("##############################################\n");

  return context;
}

Our config manifest now looks like:

# configs/dev.context.yaml
objective: &name demo
hello: world!
repeat: *name

Our bin.ts manifest:

Isn’t it beautiful ? 😙

// bin.ts
#!/usr/bin/env node
import { App, Tags } from "aws-cdk-lib";
import { AwsCdkContextDemoStack } from "../lib/aws-cdk-context-demo-stack";
import { loadConfig } from "./utils";

const app = new App();

// Load context dynamically based on environment
const env = app.node.tryGetContext("env") as string;
const context = loadConfig(env);

// Pass context to the construct
new AwsCdkContextDemoStack(app, `${env}-AwsCdkContextDemoStack`, {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION,
  },
  ...context, // Expand the context
});

We’re all set, we can now invoke the cdk with a single context key-value pair, our env , and everything else would fall into it’s place.

cdk synth --context env=dev

level 4

level 4

BONUS — CDK NAG

If you wish to take your skills to the next level, follow best practices while developing CDK applications or if you must comply to regulations and standard, the cdk-nag is a package you must use!

This package contains many rules for compliance, as well as aws recommended practices, to help you write better code and stick to the aws well-architected principals.

Summary

In this article we had learned about the rules the cdk.json and cdk.context.json files play in our AWS CDK application structure, de-coupled the configurations from the application, improved our skills and learned how to better manage our configuration, regardless of how many environment we manage today, tomorrow or a couple of year from now.

The code together with examples can be found in my GitHub repository, feel free to fork, STAR, share and replicate.

- We ❤ Open Source!

As always, great to have you, and see you next time 😃


메타데이터
post_id
a8e1a5dc7b6f
slug
aws-cdk-v2-enhanced-context-management-a8e1a5dc7b6f
url
https://towardsdev.com/aws-cdk-v2-enhanced-context-management-a8e1a5dc7b6f
canonical_url
https://towardsdev.com/aws-cdk-v2-enhanced-context-management-a8e1a5dc7b6f
author_url
https://medium.com/@lior.dux
status
ok
fetched_at
2026-06-26 03:39:16