The CDK Pattern That Saved My Sanity

After deploying the same stack to 4 environments with slightly different configs and watching the copy-paste entropy grow, I finally built the pattern I should have started with.

The Problem

You have dev, staging, prod, and that weird sandbox account nobody admits to using. Each needs slightly different config but the same infrastructure shape.

The Pattern

// lib/config.ts
interface EnvironmentConfig {
  readonly account: string;
  readonly region: string;
  readonly domainName: string;
  readonly certificateArn: string;
  readonly alertEmail?: string;
}

const configs: Record<string, EnvironmentConfig> = {
  dev: {
    account: '111111111111',
    region: 'us-east-1',
    domainName: 'dev.example.com',
    certificateArn: 'arn:aws:acm:us-east-1:111111111111:certificate/xxx',
  },
  prod: {
    account: '222222222222',
    region: 'us-east-1',
    domainName: 'example.com',
    certificateArn: 'arn:aws:acm:us-east-1:222222222222:certificate/yyy',
    alertEmail: 'ops@example.com',
  },
};

export function getConfig(env: string): EnvironmentConfig {
  const config = configs[env];
  if (!config) throw new Error(`Unknown environment: ${env}`);
  return config;
}
// lib/app-stack.ts
import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

interface AppStackProps extends StackProps {
  readonly config: EnvironmentConfig;
}

export class AppStack extends Stack {
  constructor(scope: Construct, id: string, props: AppStackProps) {
    super(scope, id, props);
    const { config } = props;

    // Everything uses config — no conditionals, no env checks
    const distribution = new CloudFrontDistribution(this, 'CDN', {
      domainName: config.domainName,
      certificateArn: config.certificateArn,
    });

    if (config.alertEmail) {
      new AlarmNotification(this, 'Alerts', {
        email: config.alertEmail,
        distribution,
      });
    }
  }
}

The key insight: config is data, not logic. The stack doesn’t know what environment it’s in. It just builds what the config describes.

Why This Works

  • Adding an environment = adding a config entry
  • No if (env === 'prod') scattered through constructs
  • Type safety catches missing config at compile time
  • Each environment is independently deployable

I help teams design and implement cloud infrastructure that doesn’t make them want to quit. fitzpatricksoftware.com

I help teams modernize legacy systems with AI-assisted development guardrails that keep the agents productive and the production safe. fitzpatricksoftware.com