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:

  1. Install the Nx Console VSCode Plugin if you haven't already
  2. Open the Nx Console in VSCode
  3. Click Generate (UI) in the "Common Nx Commands" section
  4. Search for @aws/nx-plugin - ts#infra
  5. Fill in the required parameters
    • Click Generate
    Parameter Type Default Description
    name Required string - The name of the application.
    directory string packages The directory of the new application.
    enableStageConfig boolean Enable centralized stage configuration (credentials, account, region) for multi-environment CDK deployments.

    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
    • project.json Project configuration and build targets
    • checkov.yml Checkov configuration file

    If you set the enableStageConfig 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,
    },
    });

    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.

    If you generated with enableStageConfig, 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 enableStageConfig)
    import stagesConfig from ':my-scope/common-infra-config';
    const projectStages = stagesConfig.projects?.['packages/infra']?.stages ?? {};
    const sandboxConfig = projectStages['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 enableStageConfig 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 run infra:deploy 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 = projectStages['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 * as cdk from 'aws-cdk-lib';
    import { Construct } from 'constructs';
    import { MyApi } from ':my-scope/common-constructs';
    export class ApplicationStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    // Add infrastructure for your API
    new MyApi(this, 'MyApi', {
    integrations: MyApi.defaultIntegrations(this).build(),
    });
    }
    }

    If you have used the CloudScape 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 * as cdk from 'aws-cdk-lib';
    import { Construct } from 'constructs';
    import { MyWebsite } from ':my-scope/common-constructs';
    export class ApplicationStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    // Add infrastructure for your website
    new MyWebsite(this, 'MyWebsite');
    }
    }

    It is important to ensure that the website is declared after any API constructs in order for the website Runtime Config to include all API config.

    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 run <my-infra>:synth

    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 run <my-infra>:checkov

    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.

    After a build, you can deploy your infrastructure to AWS using the deploy target.

    First, make sure you have AWS credentials configured. If you generated with enableStageConfig 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.

    Then run the deploy target:

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

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

    Terminal window
    pnpm nx run <my-infra>:deploy-ci 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 run <my-infra>:destroy <my-infra>-sandbox/*

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