This guide explains how to configure CDK Utils for different environments and deployment stages.
The primary configuration interface. All properties are passed through the CDK context.
| Property | Type | Required | Description |
|---|---|---|---|
name |
string |
Yes | Stack/construct identifier |
stage |
string |
Yes | Deployment stage (e.g., dev, tst, uat, prd) |
region |
string |
Yes | AWS region for deployment |
domainName |
string |
Yes | Base domain name (e.g., example.com) |
subDomain |
string |
No | Subdomain prefix (e.g., api) |
stackName |
string |
No | Override the CloudFormation stack name |
extraContexts |
string[] |
No | Paths to additional context JSON files |
regionContextPath |
string |
No | Directory containing region-specific context files (AWS and Azure only) |
stageContextPath |
string |
No | Directory containing stage context files (default: cdk-env for AWS, pulumi-env for Azure) |
stageRegionContextPath |
string |
No | Directory containing stage-region-specific context files (AWS and Azure only) |
| Property | Type | Default | Description |
|---|---|---|---|
globalPrefix |
string |
— | Prefix applied to all resource names |
globalSuffix |
string |
— | Suffix applied to all resource names |
resourcePrefix |
string |
— | Project-level prefix for resource names |
resourceSuffix |
string |
— | Project-level suffix for resource names |
resourceProjectIdentifier |
string |
— | Project identifier for cross-project references |
resourceNameOptions |
object |
— | Per-resource naming overrides (see below) |
| Property | Type | Default | Description |
|---|---|---|---|
nodejsRuntime |
Runtime |
NODEJS_24_X |
Lambda Node.js runtime version |
logRetention |
RetentionDays |
— | CloudWatch log retention period |
defaultReservedLambdaConcurrentExecutions |
number |
— | Default reserved concurrency for Lambda |
defaultTracing |
Tracing |
— | Default X-Ray tracing mode for Lambda |
| Property | Type | Default | Description |
|---|---|---|---|
excludeDomainNameForBuckets |
boolean |
false |
Exclude domain name from S3 bucket names |
excludeAccountNumberForBuckets |
boolean |
false |
Exclude AWS account number from bucket names |
| Property | Type | Default | Description |
|---|---|---|---|
skipStageForARecords |
boolean |
false |
Skip stage prefix in Route53 A records (useful for production) |
Create a cdk-env/ directory in your project root with a JSON file per stage:
my-project/
├── cdk.json
├── cdk-env/
│ ├── dev.json
│ ├── tst.json
│ ├── uat.json
│ └── prd.json
cdk-env/tst.json:
{
"stage": "tst",
"subDomain": "test",
"resourcePrefix": "myapp",
"apiSubDomain": "api",
"siteSubDomain": "site",
"logLevel": "debug",
"nodeEnv": "development",
"timezone": "UTC",
"useExistingHostedZone": true
}
stage value from context (typically passed via -c stage=tst)CommonStack looks for cdk-env/{stage}.jsonOverride the default cdk-env/ directory:
// cdk.json
{
"context": {
"stageContextPath": "cdk-environments"
}
}
Configuration is loaded in layers, where each layer overrides the previous:
1. Base props (lowest priority)
2. Extra contexts
3. Region context
4. Stage context
5. Stage-region context (highest priority)
Region, stage, and stage-region contexts all follow the same convention — a directory path combined with keys to auto-resolve the file:
| Layer | Directory config | Key(s) | Resolves to |
|---|---|---|---|
| Region (AWS) | regionContextPath |
region |
cdk-region/eu-west-1.json |
| Region (Azure) | regionContextPath |
location |
pulumi-region/uksouth.json |
| Stage (AWS) | stageContextPath |
stage |
cdk-env/dev.json |
| Stage (Azure) | stageContextPath |
stage |
pulumi-env/dev.json |
| Stage-region (AWS) | stageRegionContextPath |
stage + region |
cdk-env-region/dev.eu-west-1.json |
| Stage-region (Azure) | stageRegionContextPath |
stage + location |
pulumi-env-region/dev.uksouth.json |
If the resolved file doesn't exist, the layer is silently skipped.
logLevel: "debug", prod gets logLevel: "error")Layer additional configuration files for shared settings:
// cdk.json
{
"context": {
"extraContexts": [
"cdk-config/shared.json",
"cdk-config/secrets.json"
]
}
}
Extra contexts are loaded first. Region and stage contexts take precedence over extra contexts for any overlapping properties.
Layer region-specific configuration between extra contexts and stage contexts. This is useful for multi-region deployments where resources need different configuration per region (e.g., resource prefixes, location settings, SKU availability).
Set regionContextPath to a directory containing one JSON file per region/location. The stack automatically picks the right one based on the current region (AWS) or location (Azure):
cdk-region/ pulumi-region/
├── eu-west-1.json ├── uksouth.json
├── us-east-1.json └── westeurope.json
└── ap-southeast-1.json
Optionally, set stageRegionContextPath to a separate directory for stage+region overrides. These files use the {stage}.{region}.json naming convention:
cdk-env-region/ pulumi-env-region/
├── dev.eu-west-1.json ├── dev.uksouth.json
├── prd.eu-west-1.json ├── prd.uksouth.json
└── prd.us-east-1.json └── prd.westeurope.json
If a resolved file doesn't exist, the layer is silently skipped.
Region contexts are supported in AWS and Azure stacks. Cloudflare does not have a region concept and does not support region contexts.
{
"context": {
"region": "eu-west-1",
"stage": "dev",
"extraContexts": ["cdk-config/shared.json"],
"regionContextPath": "cdk-region",
"stageContextPath": "cdk-env",
"stageRegionContextPath": "cdk-env-region"
}
}
This resolves:
cdk-region/eu-west-1.jsoncdk-env/dev.jsoncdk-env-region/dev.eu-west-1.jsonname: my-azure-project
runtime: nodejs
config:
location: uksouth
stage: dev
extraContexts:
- pulumi-config/shared.json
regionContextPath: pulumi-region
stageContextPath: pulumi-env
stageRegionContextPath: pulumi-env-region
This resolves:
pulumi-region/uksouth.jsonpulumi-env/dev.jsonpulumi-env-region/dev.uksouth.jsoncdk-config/shared.json — base config defines a Lambda with modest defaults:
{
"graphqlApi": {
"functionName": "graphql-server",
"memorySize": 512,
"timeoutInSecs": 60,
"logRetentionInDays": 7
},
"siteBucket": {
"bucketName": "site",
"logBucketName": "site-logs"
}
}
cdk-region/eu-west-1.json — EU region overrides with higher memory and longer retention:
{
"region": "eu-west-1",
"resourcePrefix": "ge-eu-west-1",
"subDomain": "eu",
"apiSubDomain": "api-eu",
"graphqlApi": {
"functionName": "graphql-server",
"memorySize": 1024,
"timeoutInSecs": 300,
"logRetentionInDays": 30
},
"siteBucket": {
"bucketName": "site-eu",
"logBucketName": "site-logs-eu"
}
}
cdk-region/us-east-1.json — US primary region overrides with even higher memory and longer retention:
{
"region": "us-east-1",
"resourcePrefix": "ge-us-east-1",
"subDomain": "us",
"apiSubDomain": "api-us",
"graphqlApi": {
"functionName": "graphql-server",
"memorySize": 2048,
"timeoutInSecs": 300,
"logRetentionInDays": 90
},
"siteBucket": {
"bucketName": "site-us",
"logBucketName": "site-logs-us"
}
}
cdk-env-region/dev.eu-west-1.json — dev in EU gets reduced resources for cost savings:
{
"logLevel": "trace",
"resourcePrefix": "ge-dev-eu-west-1",
"graphqlApi": {
"functionName": "graphql-server",
"memorySize": 256,
"timeoutInSecs": 10,
"logRetentionInDays": 1
}
}
pulumi-config/shared.json — base config defines a Function App with a Basic SKU:
{
"functionApp": {
"name": "api-function-app",
"resourceGroupName": "rg-shared",
"kind": "functionapp",
"sku": {
"name": "B1",
"tier": "Basic"
}
}
}
pulumi-region/uksouth.json — UK South overrides with Elastic Premium for production-grade workloads:
{
"location": "uksouth",
"resourcePrefix": "ge-uksouth",
"subDomain": "uk",
"locationConfig": {
"uksouth": {
"id": "uksouth",
"name": "UK South"
}
},
"functionApp": {
"name": "api-function-app",
"resourceGroupName": "rg-uksouth",
"kind": "functionapp",
"sku": {
"name": "EP1",
"tier": "ElasticPremium"
}
}
}
pulumi-region/westeurope.json — West Europe overrides with a higher Elastic Premium tier for the primary region:
{
"location": "westeurope",
"resourcePrefix": "ge-westeurope",
"subDomain": "eu",
"locationConfig": {
"westeurope": {
"id": "westeurope",
"name": "West Europe"
}
},
"functionApp": {
"name": "api-function-app",
"resourceGroupName": "rg-westeurope",
"kind": "functionapp",
"sku": {
"name": "EP2",
"tier": "ElasticPremium"
}
}
}
pulumi-env-region/dev.uksouth.json — dev in UK South downgrades to Basic for cost savings:
{
"logLevel": "trace",
"resourcePrefix": "ge-dev-uksouth",
"functionApp": {
"name": "api-function-app",
"resourceGroupName": "rg-dev-uksouth",
"kind": "functionapp",
"sku": {
"name": "B1",
"tier": "Basic"
}
}
}
my-aws-project/
├── cdk.json
├── cdk-config/ # Extra contexts (shared config)
│ └── shared.json
├── cdk-region/ # Region contexts (per region, all stages)
│ ├── eu-west-1.json
│ └── us-east-1.json
├── cdk-env/ # Stage contexts (per stage, all regions)
│ ├── dev.json
│ ├── tst.json
│ ├── uat.json
│ └── prd.json
└── cdk-env-region/ # Stage-region contexts (per stage+region combo)
├── dev.eu-west-1.json
└── prd.us-east-1.json
my-azure-project/
├── Pulumi.yaml
├── pulumi-config/ # Extra contexts (shared config)
│ └── shared.json
├── pulumi-region/ # Region contexts (per location, all stages)
│ ├── uksouth.json
│ └── westeurope.json
├── pulumi-env/ # Stage contexts (per stage, all locations)
│ ├── dev.json
│ ├── tst.json
│ ├── uat.json
│ └── prd.json
└── pulumi-env-region/ # Stage-region contexts (per stage+location combo)
├── dev.uksouth.json
└── prd.westeurope.json
Deploy to a different region without changing any config — just set the region/location value:
# AWS — deploys with cdk-region/us-east-1.json
cdk deploy -c region=us-east-1 -c stage=prd
# Azure — deploys with pulumi-region/westeurope.json
pulumi config set location westeurope
pulumi up
Azure — base defines a Basic SKU, region promotes to Elastic Premium, stage overrides the subdomain, stage-region pins a specific SKU for dev+uksouth:
| Property | Base | Region (uksouth) |
Stage (dev) |
Stage-Region (dev.uksouth) |
Result |
|---|---|---|---|---|---|
functionApp.sku.name |
B1 |
EP1 |
— | B1 |
B1 (stage-region wins) |
functionApp.sku.tier |
Basic |
ElasticPremium |
— | Basic |
Basic (stage-region wins) |
functionApp.resourceGroupName |
rg-shared |
rg-uksouth |
— | rg-dev-uksouth |
rg-dev-uksouth (stage-region wins) |
location |
— | uksouth |
— | — | uksouth (region survives) |
subDomain |
— | uk |
dev |
— | dev (stage survives) |
logLevel |
error |
warn |
debug |
trace |
trace (stage-region wins) |
AWS — base defines a 512 MB Lambda, region scales up to 1024 MB, stage overrides the prefix, stage-region scales down to 256 MB for test+eu-west-1:
| Property | Base | Region (eu-west-1) |
Stage (tst) |
Stage-Region (tst.eu-west-1) |
Result |
|---|---|---|---|---|---|
graphqlApi.memorySize |
512 |
1024 |
— | 256 |
256 (stage-region wins) |
graphqlApi.logRetentionInDays |
7 |
30 |
— | 1 |
1 (stage-region wins) |
resourcePrefix |
ge |
ge-eu-west-1 |
cdktest |
ge-test-eu-west-1 |
ge-test-eu-west-1 (stage-region wins) |
subDomain |
— | eu |
tst |
— | tst (stage survives) |
logLevel |
error |
warn |
debug |
trace |
trace (stage-region wins) |
[globalPrefix]-[resourcePrefix]-resourceName-[resourceSuffix]-[globalSuffix]-stage
With resourcePrefix: "myapp" and stage: "dev":
| Resource Name | Formatted Output |
|---|---|
user-table |
myapp-user-table-dev |
api-function |
myapp-api-function-dev |
With globalPrefix: "acme", resourcePrefix: "myapp", and stage: "prd":
| Resource Name | Formatted Output |
|---|---|
user-table |
acme-myapp-user-table-prd |
Use resourceNameOptions to customise naming for specific resources:
{
"resourceNameOptions": {
"my-special-bucket": {
"prefix": "custom",
"suffix": "v2",
"globalPrefix": false,
"globalSuffix": false
},
"legacy-table": {
"exclude": true
}
}
}
| Option | Type | Description |
|---|---|---|
exclude |
boolean |
Skip all prefixes/suffixes, only append stage |
prefix |
string |
Override resourcePrefix for this resource |
suffix |
string |
Override resourceSuffix for this resource |
globalPrefix |
boolean |
Include/exclude the global prefix |
globalSuffix |
boolean |
Include/exclude the global suffix |
The fully qualified domain name is computed as:
{subDomain}.{domainName}
For stage-aware domains (Route53 A records):
{stage}.{subDomain}.{domainName} (e.g., tst.api.example.com){subDomain}.{domainName} (e.g., api.example.com){
"app": "npx ts-node --prefer-ts-exts src/main.ts",
"context": {
"stage": "dev",
"domainName": "example.com",
"subDomain": "api",
"region": "eu-west-1",
"globalPrefix": "acme",
"resourcePrefix": "myapp",
"stageContextPath": "cdk-env",
"extraContexts": [],
"regionContextPath": "cdk-region",
"stageRegionContextPath": "cdk-env-region"
}
}
Deploy with a different stage:
cdk deploy -c stage=prd
Azure uses the Pulumi Azure Native provider (@pulumi/azure-native), which provides 100% API coverage of Azure Resource Manager. This is the recommended provider over the classic @pulumi/azure provider.
CommonAzureStackProps shares the same base properties (stage, domainName, subDomain, name) from @gradientedge/cdk-utils-common, plus Azure-specific options.
name: my-azure-project
runtime: nodejs
config:
stage: dev
domainName: example.com
location: uksouth
resourcePrefix: myapp
regionContextPath: pulumi-region
stageRegionContextPath: pulumi-env-region
The AzureResourceNameFormatter follows the same pattern as AWS:
[globalPrefix]-[resourcePrefix]-resourceName-[resourceSuffix]-[globalSuffix]-stage
Key difference: Azure handles undefined resource names gracefully (converts to empty string).
| Property | Type | Description |
|---|---|---|
resourceGroup |
ResourceGroup |
The Azure resource group for all resources |
commonLogAnalyticsWorkspace |
Workspace |
Shared Log Analytics workspace |
Cloudflare uses CommonCloudflareStackProps with the same shared base properties. Cloudflare is a global anycast network and does not have a region concept, so regionContextPath is not supported for Cloudflare stacks.
name: my-cloudflare-project
runtime: nodejs
config:
stage: dev
domainName: example.com
Cloudflare constructs use Pulumi's native naming conventions rather than a custom formatter.