Skip to content

CDK Infrastructure

AWS CDK is a framework for defining cloud infrastructure in code and provisioning it through AWS CloudFormation.

The TypeScript infrastructure generator creates an AWS CDK infrastructure application written in TypeScript. The generated application includes security best practices through Checkov security checks.

You can generate a new infrastructure project in two ways:

Terminal window
pnpm nx g @aws/nx-plugin:ts#infra
You can also perform a dry-run to see what files would be changed
Terminal window
pnpm nx g @aws/nx-plugin:ts#infra --dry-run
ParameterTypeDefaultDescription
name Requiredstring-The name of the application.
directory stringpackagesThe directory of the new application.
subDirectory string-The sub directory the project is placed in. By default this is the project name.
stageConfig booleanEnable centralized stage configuration (credentials, account, region) for multi-environment CDK deployments.
preferInstallDependencies booleantrueWhether to prefer installing dependencies after the generator runs. Set to false to defer installing when batching multiple generators (an install still runs if needed so subsequent generators can compute the Nx project graph); install once at the end.

The generator will create the following project structure in the <directory>/<name> directory:

  • Directorysrc
    • main.ts Application entry point instantiating CDK stages to deploy
    • Directorystages CDK Stage definitions
      • application-stage.ts Defines a collection of stacks to deploy in a stage
    • Directorystacks CDK Stack definitions
      • application-stack.ts Main application stack
  • cdk.json CDK configuration
  • package.json Project manifest defining the project’s package name and dependencies
  • project.json Project configuration and build targets
  • checkov.yml Checkov configuration file

If you set the stageConfig option, the generator also creates two shared packages for centralized credential management (if they don’t already exist):

  • Directorypackages/common
    • Directoryinfra-config Stage configuration types and credential mappings
      • Directorysrc
        • stages.types.ts Type definitions for stage credentials and config
        • stages.config.ts Your stage-to-credential mappings (edit this)
        • index.ts Re-exports for importing from other packages
    • Directoryscripts Centralized deploy/destroy scripts
      • Directorysrc
        • infra-deploy.ts Deploy bin script
        • infra-destroy.ts Destroy bin script
        • Directorystage-credentials/ Shared logic (credential lookup, CDK command building)

You can start writing your CDK infrastructure inside src/stacks/application-stack.ts, for example:

src/stacks/application-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Bucket } from 'aws-cdk-lib/aws-s3'
import { Construct } from 'constructs';
export class ApplicationStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Declare your infrastructure here
new Bucket(this, 'MyBucket');
}
}

CDK uses Stages to group stacks that should be deployed together to a specific environment. The generated src/main.ts creates a sandbox stage for your own development and testing:

src/main.ts
new ApplicationStage(app, 'my-app-sandbox', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
// Define other instances of stages, such as beta and prod, below

The env property tells CDK which AWS account and region to deploy to. CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION are resolved automatically by the CDK CLI from your active AWS credentials. See the CDK environments documentation for more details.

The sandbox stage is the one the deploy-sandbox target deploys.

If you generated with stageConfig, the main.ts reads account and region from a centralized config file instead, falling back to environment variables when no config is set:

src/main.ts (with stageConfig)
import { resolveStage } from '@my-scope/common-infra-config';
// Looks up the stage under this project (packages/infra), falling back to
// shared stages. Returns undefined when no config exists for the stage.
const sandboxConfig = resolveStage('packages/infra', 'my-app-sandbox');
new ApplicationStage(app, 'my-app-sandbox', {
env: {
account: sandboxConfig?.account ?? process.env.CDK_DEFAULT_ACCOUNT,
region: sandboxConfig?.region ?? process.env.CDK_DEFAULT_REGION,
},
});

You can add more stages to deploy to different environments. For example, beta and prod stages targeting separate AWS accounts:

src/main.ts
new ApplicationStage(app, 'project-beta', {
env: {
account: '123456789012',
region: 'us-west-2',
},
});
new ApplicationStage(app, 'project-prod', {
env: {
account: '098765432109',
region: 'us-west-2',
},
});

A Stage groups one or more stacks. You can add as many stacks as you need inside a stage:

src/stages/application-stage.ts
import { Stage, StageProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { BackendStack } from '../stacks/backend-stack.js';
import { FrontendStack } from '../stacks/frontend-stack.js';
export class ApplicationStage extends Stage {
constructor(scope: Construct, id: string, props?: StageProps) {
super(scope, id, props);
new BackendStack(this, 'Backend', {
crossRegionReferences: true,
})
new FrontendStack(this, 'Frontend', {
crossRegionReferences: true,
});
}
}

When you have multiple stages targeting different AWS accounts, managing credentials manually can be error-prone, especially as the number of stages grows.

The stageConfig option solves this by generating two shared packages:

  • packages/common/infra-config — A single config file where you map each stage to its AWS credentials, account, and region. This is importable from any package in your workspace, so your CDK main.ts can read account and region from the same source of truth.
  • packages/common/scriptsinfra-deploy and infra-destroy commands that wrap CDK with automatic credential resolution. When you run deploy, the script reads the config, sets the right AWS environment variables for the CDK child process, and runs cdk deploy. Your shell environment is never modified.

Edit packages/common/infra-config/src/stages.config.ts to map your stages to AWS credentials:

packages/common/infra-config/src/stages.config.ts
import type { StagesConfig } from './stages.types.js';
const config: StagesConfig = {
projects: {
// The key is the project path relative to the workspace root.
// This matches the path in project.json and in deploy commands.
'packages/infra': {
stages: {
// Stage names must match the CDK stage identifiers in main.ts
// (the first argument to `new ApplicationStage(app, 'my-app-dev', ...)`).
'my-app-dev': {
credentials: { type: 'profile', profile: 'dev-account' },
region: 'us-east-1',
},
'my-app-prod': {
credentials: {
type: 'assumeRole',
assumeRole: 'arn:aws:iam::123456789012:role/DeployRole',
},
region: 'us-west-2',
account: '123456789012',
},
},
},
},
shared: {
// Shared stages are available to all infra projects.
// Project-specific entries take priority over shared ones.
stages: {
sandbox: {
credentials: { type: 'profile', profile: 'personal-sandbox' },
region: 'us-east-1',
},
},
},
};
export default config;

When you deploy, for example:

Terminal window
pnpm nx deploy infra my-app-dev/*

The deploy script:

  1. Extracts the stage name my-app-dev from the command arguments
  2. Looks up credentials in the config: first under projects['packages/infra'], then under shared
  3. If found, sets AWS_PROFILE (or assumes the IAM role) for the CDK child process only
  4. If not found, falls back to whatever AWS credentials are in your environment

This means existing workflows without any config continue to work — the script only applies credentials when it finds a matching entry.

Two credential strategies are supported:

  • profile — Uses a named AWS CLI profile from ~/.aws/config. The script sets AWS_PROFILE for the CDK process.
  • assumeRole — Calls STS AssumeRole with the specified role ARN and passes the temporary credentials to CDK. You can optionally specify a profile as the source credentials for the AssumeRole call, an externalId for cross-account trust policies, and a sessionDuration in seconds.

Each stage config includes a required region and an optional account:

  • region (required) — The AWS region to deploy to (e.g., us-east-1, eu-west-2).
  • account (optional) — The AWS account ID. If omitted, CDK infers it from the active credentials at deploy time. See the CDK environments documentation for how CDK resolves account and region.

The generated main.ts reads these values from the config so that CDK synthesis and deployment use the same environment settings:

src/main.ts
const sandboxConfig = resolveStage('packages/infra', 'my-app-sandbox');
new ApplicationStage(app, 'my-app-sandbox', {
env: {
account: sandboxConfig?.account ?? process.env.CDK_DEFAULT_ACCOUNT,
region: sandboxConfig?.region ?? process.env.CDK_DEFAULT_REGION,
},
});

Shared stages (under shared.stages) apply to any infra project in the workspace. This is useful when multiple projects deploy to the same sandbox account — you define the credentials once instead of repeating them for each project.

Project-specific stages (under projects['packages/infra'].stages) only apply to that project. When both exist for the same stage name, the project-specific entry takes priority.

If you have used the tRPC API or FastAPI generators to create APIs, you will notice you already have some constructs available in packages/common/constructs to deploy them.

If, for example, you created a tRPC API called my-api, you can simply import and instantiate the construct to add all necessary infrastructure to deploy it:

src/stacks/application-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { MyApi } from '@my-scope/common-constructs';
export class ApplicationStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Add infrastructure for your API
new MyApi(this, 'MyApi', {
integrations: MyApi.defaultIntegrations(this).build(),
});
}
}

If you have used the React Website generator, you will notice you already have a construct in packages/common/constructs to deploy it. For example:

src/stacks/application-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { MyWebsite } from '@my-scope/common-constructs';
export class ApplicationStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// Add infrastructure for your website
new MyWebsite(this, 'MyWebsite');
}
}

As part of your build target, as well as running the default compile, lint and test targets, your infrastructure project is synthesized to CloudFormation. This can also be executed in a standalone fashion, by running the synth target:

Terminal window
pnpm nx synth <my-infra>

You will find your synthesized cloud assembly in the root dist folder, under dist/packages/<my-infra-project>/cdk.out.

A checkov target is added to your project which runs security checks on your infrastructure using Checkov.

Terminal window
pnpm nx checkov <my-infra>

You will find your security test results in the root dist folder, under dist/packages/<my-infra-project>/checkov.

There may be instances where you want to suppress certain rules on resources. You can do this in two ways:

import { suppressRules } from '@my-scope/common-constructs';
// suppresses the CKV_AWS_XXX for the given construct.
suppressRules(construct, ['CKV_AWS_XXX'], 'Reason');
import { suppressRules } from '@my-scope/common-constructs';
// Supresses the CKV_AWS_XXX for the construct or any of its descendants if it is an instance of Bucket
suppressRules(construct, ['CKV_AWS_XXX'], 'Reason', (construct) => construct instanceof Bucket);

If you are deploying a CDK application to an AWS Account for the first time, it will need to be bootstrapped. Bootstrapping creates the resources CDK needs to manage deployments (an S3 bucket for assets, IAM roles, etc.).

First, ensure that you have configured credentials for your AWS account.

Next, run the bootstrap command for each account and region you plan to deploy to:

Terminal window
npx cdk bootstrap aws://<account-id>/<region>

For more details, please refer to the CDK bootstrapping documentation.

Your project has three deploy targets, each suited to a different situation:

TargetUse it for
deploy-sandboxDeploying your own sandbox stage during development. No stage argument needed.
deployDeploying any stage, by naming the stage or stacks you want.
deploy-ciDeploying from a CI/CD pipeline, using a pre-synthesized cloud assembly.

First, make sure you have AWS credentials configured. If you generated with stageConfig and have configured stage credentials in packages/common/infra-config/src/stages.config.ts, the deploy command will automatically resolve and apply the correct credentials for the target stage. Otherwise, ensure your AWS credentials are set in your environment (e.g., via AWS_PROFILE or environment variables). See the AWS credentials documentation for the available options.

The deploy-sandbox target deploys the sandbox stage that main.ts declares, so you don’t need to remember its stage name:

Terminal window
pnpm nx deploy-sandbox <my-infra>

This is the quickest way to get your own copy of the application running in AWS while you develop.

The deploy target deploys whichever stage or stacks you name. Use it for stages other than your sandbox, or to deploy a single stack:

Terminal window
pnpm nx deploy <my-infra> <my-infra>-sandbox/*

You can specify any stage so long as it is defined in main.ts. To deploy an individual stack, give the full stack name:

Terminal window
pnpm nx deploy <my-infra> <my-infra>-sandbox/Application

Use the deploy-ci target if you are deploying to AWS as part of a CI/CD pipeline.

Terminal window
pnpm nx deploy-ci <my-infra> my-stage/*

This target differs slightly from the regular deploy target in that it deploys a pre-synthesized cloud assembly rather than synthesizing on the fly. This avoids potential non-determinism from package version changes, ensuring that every pipeline stage deploys using the same cloud assembly.

Use the destroy target to tear down your resources:

Terminal window
pnpm nx destroy <my-infra> <my-infra>-sandbox/*

For more information about CDK, please refer to the CDK Developer Guide and API Reference.