The cdk.json file is more than just a configuration dump; it’s the primary mechanism for injecting runtime context into your CDK application, dictating how your infrastructure is synthesized and deployed.
Let’s see this in action. Imagine you have an application that needs to provision different SQS queues based on the environment it’s being deployed to.
Here’s a snippet of cdk.json:
{
"app": "npx ts-node bin/my-app.ts",
"context": {
"environment": "dev",
"featureFlags": {
"newSQSImplementation": false
}
}
}
And in your bin/my-app.ts:
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { MyAppStack } from '../lib/my-app-stack';
const app = new cdk.App();
const env = app.node.getContext('environment');
const featureFlags = app.node.getContext('featureFlags');
new MyAppStack(app, 'MyAppStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
context: { // This context is passed to the Stack, not the App
environment: env,
featureFlags: featureFlags
}
});
And within lib/my-app-stack.ts:
import * as cdk from 'aws-cdk-lib';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import { Construct } from 'constructs';
export class MyAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps & { context: any }) {
super(scope, id, props);
const environment = props.context.environment;
const featureFlags = props.context.featureFlags;
if (environment === 'dev') {
new sqs.Queue(this, 'MyQueue', {
queueName: 'dev-my-queue',
});
} else if (environment === 'prod') {
new sqs.Queue(this, 'MyQueue', {
queueName: 'prod-my-queue',
visibilityTimeout: cdk.Duration.seconds(30),
});
}
if (featureFlags.newSQSImplementation) {
new sqs.Queue(this, 'NewQueue', {
queueName: `${environment}-new-queue`,
});
}
}
}
Running cdk synth with the above cdk.json will generate CloudFormation that includes a queue named dev-my-queue. If you change "environment": "dev" to "environment": "prod", the queue name becomes prod-my-queue and gains a VisibilityTimeout. If you then flip "newSQSImplementation": false to "newSQSImplementation": true, a second queue, dev-new-queue (or prod-new-queue if environment is prod), will appear.
The cdk.json file serves as the central control panel for your CDK application’s behavior at synthesis time. The context object within it is a key-value store that gets injected into the cdk.App instance. You can then access this context within your application code using app.node.getContext('keyName'). This is crucial for making your infrastructure dynamic and environment-aware without hardcoding values directly into your stack definitions.
You can also override context values directly from the command line using the -c or --context flag. For example, to deploy to production and enable the new SQS implementation:
cdk deploy -c environment=prod -c featureFlags.newSQSImplementation=true
This command-line override takes precedence over the values set in cdk.json, providing a flexible way to manage configurations for different deployment scenarios. The cdk.json is evaluated first, then command-line context flags are applied, effectively merging the configurations.
The context object can hold any JSON-serializable data. This includes simple strings, numbers, booleans, arrays, and nested objects. This flexibility allows you to manage complex configurations, such as feature flags, API endpoints, or database connection strings, in a structured manner.
When you access context using app.node.getContext('featureFlags'), you retrieve the entire featureFlags object as it’s defined in cdk.json or provided via the command line. Your application code then parses this object to enable or disable specific features. This pattern is commonly used for implementing canary deployments, A/B testing infrastructure, or gradually rolling out new services.
The context values are available during the synthesis phase of the CDK workflow. This means they influence the CloudFormation template that is generated. They are not runtime environment variables for your deployed AWS resources unless you explicitly pass them as such (e.g., by setting an environment variable for a Lambda function).
The app key in cdk.json specifies the entry point of your CDK application. It’s typically a command that executes your TypeScript or JavaScript code, like npx ts-node bin/my-app.ts. The context key holds the key-value pairs that are passed to your CDK application’s App instance.
You can think of the context as a way to parameterize your CDK application. Instead of writing a new stack for each environment, you write one parameterized stack and use context to provide the environment-specific values. This promotes reusability and reduces duplication.
The most surprising true thing about CDK context is that it’s fundamentally a form of configuration-driven code generation. The cdk.json and command-line context flags aren’t just settings; they are direct inputs that shape the CloudFormation template your application produces. Your CDK code is essentially a template that gets instantiated with specific parameters at synthesis time, and these parameters are primarily managed via context.
When you define a construct that relies on context, like the MyAppStack in the example, you are essentially telling CDK: "Generate CloudFormation resources based on these specific inputs." The cdk synth command then takes your code and the provided context, and outputs a static CloudFormation template.
Consider a scenario where you want to deploy a global DynamoDB table with different read/write capacities per region. You could define your cdk.json like this:
{
"app": "npx ts-node bin/my-app.ts",
"context": {
"regionConfigs": {
"us-east-1": { "readCapacity": 5, "writeCapacity": 2 },
"eu-west-1": { "readCapacity": 3, "writeCapacity": 1 }
}
}
}
And your stack would then iterate through app.node.getContext('regionConfigs') to provision the table in each specified region with its corresponding capacity.
This approach allows for sophisticated infrastructure management where a single CDK app can deploy to multiple regions with distinct configurations, all controlled by the cdk.json or command-line context.
The next concept you’ll likely encounter is how to manage these context values across multiple cdk.json files for different environments or how to integrate them with CI/CD pipelines.