Right, let’s talk about CDK Context. This is where the CDK stops being a purely declarative infrastructure-as-code tool and starts getting a bit clever, pulling in information from your actual AWS environment. It’s the mechanism that lets you write code that says, “Hey, give me the latest AMI ID for Amazon Linux 2,” or “What’s the VPC in this account I should use?” without hardcoding values that will change and break your synth.

Think of it as the CDK’s way of having a conversation with your AWS account before it generates the CloudFormation template. It looks up live data so you don’t have to. This is powerful, but as you’ll soon see, it’s also a fantastic way to shoot yourself in the foot if you don’t know how the gun works.

The .context File and the CLI Cache

When you run a command like cdk synth, the CDK needs to resolve these context lookups. The results are cached locally in a file called cdk.context.json at the root of your project. This is both a blessing and a curse.

The blessing: It makes your synth operations faster and repeatable. The CDK won’t spam the AWS APIs every single time you generate a template.

The curse: This file is part of your now-reliable-and-repeatable infrastructure definition. If you don’t check it into version control, you and your teammates will get different results when you synth, because you’ll have different cached values. This is a classic “works on my machine” problem waiting to happen.

// cdk.context.json
{
  "availability-zones:account=123456789012:region=us-east-1": ["us-east-1a", "us-east-1b", "us-east-1c"],
  "ami:account=123456789012:region=us-east-1:imageType=amazon-linux-2:virtualizationType=hvm": "ami-0abcdef1234567890"
}

The best practice here is non-negotiable: check cdk.context.json into your version control. Treat it like a lock file (package-lock.json, yarn.lock). When you need to update the cached values—say, because a new AMI is released—you use cdk context --reset <key> or the nuclear option cdk context --reset, which purges the cache. The next synth will fetch fresh values and update the file.

Performing Context Lookups in Your Code

The CDK provides methods for the most common lookups. Let’s say you need the Amazon Linux 2 AMI. Hardcoding that AMI ID is a guarantee it will be wrong in six months. Instead, you ask:

import * as ec2 from 'aws-cdk-lib/aws-ec2';

// This lookup is performed during synth and cached in cdk.context.json
const ami = ec2.MachineImage.latestAmazonLinux2();

new ec2.Instance(this, 'MyInstance', {
  instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
  machineImage: ami,
  vpc: myVpc,
});

Under the hood, the CDK is making a call to the SSM parameter store to get the value of /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2. The result of that API call is what gets stored in your context cache.

The VPC Lookup: A Common Pitfall

This one trips up everyone. You want to grab an existing VPC so you don’t create a new one every time. Seems straightforward, right?

import * as ec2 from 'aws-cdk-lib/aws-ec2';

// This looks up the VPC with ID 'vpc-123456' in your account/region
const vpc = ec2.Vpc.fromLookup(this, 'MyVpc', {
  vpcId: 'vpc-123456',
});

Here’s the absurd part: the CDK uses every property you provide in the lookup object as a filter to uniquely identify the resource. It’s not just using the vpcId. If you provide vpcId: 'vpc-123456' and tags: { Environment: 'Prod' }, it will look for a VPC with that specific ID AND those exact tags. If the tags don’t match, the lookup fails. This is the designers being… overly specific.

The kicker? The result of this lookup is also cached in cdk.context.json based on the filter parameters you provided. If someone changes a tag on that VPC, your cached context still has the old description. Your CDK app will keep synthesizing the old template until you reset the context cache. This can cause some truly confusing drift. The best practice is to keep your filters as simple and immutable as possible—like just the VPC ID.

When to Use (and Not Use) Context

Use context for values that are:

  • Environment-specific: The ID of an existing shared VPC.
  • Dynamic but well-defined: The latest AMI ID, the latest AWS-provided Lambda runtime.
  • Needed for synthesis: Anything required to make decisions in your code before CloudFormation deploys.

Do not use context for:

  • Secrets: Never. The values in cdk.context.json are plaintext and checked into git.
  • Truly dynamic values: The current time, a random number. For these, you should use CloudFormation Parameters or generate them at deployment time with Custom Resources. Context is for synth-time resolution.

The golden rule is this: if the value is required to generate the template, it’s a job for context. If it’s a value you want to provide when deploying the template, it’s a job for CloudFormation Parameters or a custom resource. Mastering this distinction is what separates a functional CDK app from a robust one.