AWS CDK in TypeScript is a powerful tool for defining your cloud infrastructure as code, but deploying it to production requires more than just writing valid TypeScript.

Here’s a look at how to make your CDK deployments robust and reliable in a production environment.

The Surprising Truth: Your CDK App is a Compiler

The most counterintuitive aspect of AWS CDK is that your TypeScript code isn’t directly what gets deployed. Instead, the CDK CLI synthesizes your entire cloud infrastructure definition into a set of CloudFormation templates. Think of the CDK app as a compiler: you write in a high-level language (TypeScript), and it outputs a lower-level, machine-readable format (CloudFormation JSON). This compilation step is where many potential issues can be caught or introduced.

Seeing the CDK in Action: A Simple S3 Bucket

Let’s see how CDK translates your intentions into CloudFormation.

// lib/my-bucket-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';

export class MyBucketStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    new s3.Bucket(this, 'MyProductionBucket', {
      versioned: true,
      encryption: s3.BucketEncryption.SOUGHT_ENCRYPTION, // This is a placeholder for actual encryption
      removalPolicy: cdk.RemovalPolicy.RETAIN,
      autoDeleteObjects: false,
    });
  }
}

When you run cdk synth, the CDK CLI processes this TypeScript code. The output isn’t TypeScript anymore; it’s a CloudFormation template.

# Example of running cdk synth
cdk synth

This command will generate a file (or print to stdout) containing the CloudFormation JSON that describes the S3 bucket. You can inspect this JSON to see exactly what CloudFormation resources CDK is creating.

// Example snippet of synthesized CloudFormation
{
  "Resources": {
    "MyProductionBucketXXXXXXXX": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "VersioningConfiguration": {
          "Status": "Enabled"
        },
        "BucketEncryption": {
          "ServerSideEncryptionConfiguration": [
            {
              "ServerSideEncryptionByDefault": {
                "SSEAlgorithm": "AES256"
              }
            }
          ]
        },
        "PublicAccessBlockConfiguration": {
          "BlockPublicAcls": true,
          "BlockPublicPolicy": true,
          "IgnorePublicAcls": true,
          "RestrictPublicBuckets": true
        }
      }
    }
  }
}

Notice how versioned: true becomes "VersioningConfiguration": { "Status": "Enabled" }, and removalPolicy: cdk.RemovalPolicy.RETAIN ensures the bucket isn’t deleted when the stack is destroyed. The autoDeleteObjects: false is crucial to prevent accidental data loss in production.

The Mental Model: From Code to CloudFormation

Your CDK application is structured as a tree of Constructs. A Stack is a top-level construct representing a single CloudFormation stack. Within a stack, you instantiate higher-level constructs (like s3.Bucket) or lower-level CloudFormation resources.

The key components of the CDK mental model are:

  • Constructs: The basic building blocks. They represent a cloud component (e.g., a VPC, an S3 bucket, a Lambda function). Constructs can be composed to create more complex infrastructure.
  • Stacks: A logical grouping of AWS resources that are deployed as a single unit by CloudFormation. Each CDK app can have one or more stacks.
  • Apps: The entry point of your CDK application. It instantiates your stacks.
  • Synthesis: The process of translating your CDK constructs into CloudFormation templates. This is done by the cdk synth command.
  • Deployment: The process of taking the synthesized CloudFormation template and deploying it to your AWS account using cdk deploy.

Production-Ready Best Practices

  1. Explicit removalPolicy: For production resources, always set removalPolicy: cdk.RemovalPolicy.RETAIN. This prevents accidental deletion of critical infrastructure (like databases or S3 buckets) when you destroy the CDK stack. cdk.RemovalPolicy.DESTROY is useful for development and testing, but a disaster in production.

    new rds.DatabaseInstance(this, 'ProductionDB', {
      // ... other properties
      removalPolicy: cdk.RemovalPolicy.RETAIN,
    });
    
  2. Enable Versioning and Encryption for S3 Buckets: Never deploy S3 buckets in production without versioning and server-side encryption enabled. This protects against accidental data deletion and ensures data at rest is secured.

    new s3.Bucket(this, 'MyDataBucket', {
      versioned: true,
      encryption: s3.BucketEncryption.SOUGHT_ENCRYPTION, // Use SOUGHT_ENCRYPTION for KMS-managed keys
      removalPolicy: cdk.RemovalPolicy.RETAIN,
      autoDeleteObjects: false, // Crucial for preventing data loss
    });
    

    Note: s3.BucketEncryption.SOUGHT_ENCRYPTION will default to SSE-S3 if no KMS key is specified, but it’s better to explicitly define a KMS key for more control.

  3. Use cdk.NagSuppressions for Security Best Practices: The cdk-nag library is essential for enforcing security compliance. You can suppress specific rules, but always add a comment explaining why you are suppressing it.

    import { Aspects } from 'aws-cdk-lib';
    import { AwsSolutionsChecks } from 'cdk-nag';
    
    const app = new cdk.App();
    const stack = new MyStack(app, 'MyStack');
    
    // Add AWS Solutions Checks for security best practices
    Aspects.of(stack).add(new AwsSolutionsChecks({ verbose: true }));
    
    // Example of suppressing a rule for a specific resource
    Aspects.of(stack).add(new AwsSolutionsChecks({
      verbose: true,
      // You'd typically suppress specific rules like:
      // nagPacks: [new AwsSolutionsPack({ suppressions: [{ id: 'AwsSolutions-S1', reason: 'Bucket is for logging, not sensitive data.' }] })]
    }));
    
  4. Manage Dependencies and Environment Variables Wisely: For Lambda functions, avoid hardcoding secrets. Use AWS Systems Manager Parameter Store or AWS Secrets Manager, and grant your Lambda function the necessary IAM permissions to access them.

    // In your Lambda function definition
    const secret = secretsmanager.Secret.fromSecretNameV2(this, 'MyApiSecret', 'my-prod-api-secret');
    const myFunction = new lambda.Function(this, 'MyFunction', {
      runtime: lambda.Runtime.NODEJS_18_X,
      handler: 'index.handler',
      code: lambda.Code.fromAsset('lambda'),
      environment: {
        API_SECRET_ARN: secret.secretArn,
      },
    });
    
    // Grant permissions to the Lambda function to read the secret
    secret.grantRead(myFunction);
    
  5. Implement Rollback Strategies: While CDK deploys changes, CloudFormation handles rollbacks on failure. Understand how your changes might interact and ensure you have a strategy for manual intervention if an automated rollback isn’t sufficient. For critical applications, consider blue/green deployments or canary releases, which can be orchestrated with CDK and other AWS services.

  6. Parameter Store for Configuration: Use AWS Systems Manager Parameter Store for environment-specific configurations (e.g., API endpoints, feature flags). This allows you to update configuration without redeploying your CDK stack.

    // Example of referencing a parameter store value
    const dbHost = ssm.StringParameter.valueFromSecureStringParameter(this, '/prod/db/host', 1);
    // ... use dbHost in your resource properties
    

The Hidden Cost of Abstraction

A common pitfall is relying too heavily on CDK abstractions without understanding the underlying CloudFormation. When things go wrong, you need to be able to inspect the synthesized CloudFormation and understand the exact AWS resources being provisioned and their properties. Over-reliance on custom constructs without rigorous testing can also lead to subtle bugs that are hard to trace back to the original CDK code.

The Next Step: Continuous Integration and Deployment (CI/CD)

Once you have your CDK application well-defined and following best practices, the next logical step is to automate its deployment using a CI/CD pipeline. This ensures consistent, repeatable, and secure deployments of your infrastructure.

Want structured learning?

Take the full Cdk course →