Files
spicy-automation/docs/CDK_SYNTH_EXAMPLES.md
Ryan Wilson 68684df471 Initial commit: Spicy CDK automation framework
Jenkins shared library and CDK constructs for AWS infrastructure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-11-18 22:21:00 -08:00

38 KiB

CDK Synth Command Examples

This document provides comprehensive examples of pnpm exec cdk synth commands for all stack types and configurations.

Prerequisites

cd spicy-automation
pnpm install
pnpm run build

Common Warnings

The following warnings are expected and non-critical:

  1. Route Table ID Warnings: When importing VPCs, route table IDs are optional. These warnings can be ignored.

    [Warning] No routeTableId was provided to the subnet 'subnet-xxx'
    
  2. Construct Metadata Warnings: CDK internal warnings about managed policies. These are informational only.

    [Warning] Failed to add construct metadata for node [ExecutionRole]
    

CloudFormation Import Pattern (Minimal Props)

The CDK implementation supports the same CloudFormation import pattern as the old Ansible/CloudFormation templates:

  • Minimal props: Only clusterName required for most deployments
  • Auto-imports: VPC ID, listener ARNs, and logs bucket auto-import from CloudFormation exports
  • No defaults: All required values must exist in exports or be provided explicitly - fails early if missing

Export chain:

  • VPC stack exports: ${vpcStackName}-VPCID, ${vpcStackName}-VPCCIDR, ${vpcStackName}-PrivateSubnetA1ID, etc.
  • Cluster stack exports: ${clusterName}-VPC, ${clusterName}-internet-facing-https-listener, ${clusterName}-logs-s3-bucket
  • ALB stack exports: ${albStackName}-internet-facing-arn, ${albStackName}-internet-facing-https-listener, ${albStackName}-internet-facing-http-listener

Important: The implementation fails early if required exports don't exist. There are no defaults - either the data exists in CloudFormation exports, or the synth fails with a clear error message.

VPC Stack

Minimal VPC (Default Configuration)

pnpm exec cdk synth \
  -c stackType=vpc \
  -c stackName=my-vpc \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=vpc

Creates:

  • VPC with default CIDR (172.1.0.0/16)
  • 4 Availability Zones
  • Public subnets (one per AZ)
  • Private subnets Type 1 (one per AZ with NAT Gateways)
  • Private subnets Type 2 (one per AZ with NACLs)
  • S3 Gateway Endpoint

Custom VPC Configuration

pnpm exec cdk synth \
  -c stackType=vpc \
  -c stackName=production-vpc \
  -c ownerTag=Platform \
  -c productTag=spicy \
  -c componentTag=network \
  -c vpcCidr=10.0.0.0/16 \
  -c numberOfAzs=3 \
  -c availabilityZones=ca-central-1a,ca-central-1b,ca-central-1c \
  -c createPrivateSubnets=true \
  -c createAdditionalPrivateSubnets=true

Development VPC (2 AZs, No NACL Subnets)

pnpm exec cdk synth \
  -c stackType=vpc \
  -c stackName=dev-vpc \
  -c ownerTag=Dev \
  -c productTag=myapp \
  -c componentTag=vpc \
  -c numberOfAzs=2 \
  -c createAdditionalPrivateSubnets=false

Public-Only VPC (No NAT Gateways)

pnpm exec cdk synth \
  -c stackType=vpc \
  -c stackName=public-vpc \
  -c ownerTag=Dev \
  -c productTag=myapp \
  -c componentTag=vpc \
  -c createPrivateSubnets=false

ECS Cluster Stack

Minimal Cluster (No Load Balancers) - Using CloudFormation Imports

Minimal props approach: Only vpcStackName required. All VPC details auto-import from VPC stack exports.

pnpm exec cdk synth \
  -c stackType=ecs-cluster \
  -c stackName=my-cluster \
  -c vpcStackName=my-vpc \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=ecs-cluster \
  -c environment=dev

What auto-imports from VPC stack:

  • VPC ID from ${vpcStackName}-VPCID
  • VPC CIDR from ${vpcStackName}-VPCCIDR
  • Number of AZs from ${vpcStackName}-NumberOfAZs
  • Private subnet IDs from ${vpcStackName}-PrivateSubnetA1ID, ${vpcStackName}-PrivateSubnetB1ID, etc.
  • Public subnet IDs from ${vpcStackName}-PublicSubnetAID, ${vpcStackName}-PublicSubnetBID, etc. (if createExternalLoadBalancer=true)
  • Availability zones auto-derived from region and number of AZs

Cluster with External ALB - Using CloudFormation Imports

Minimal props:

pnpm exec cdk synth \
  -c stackType=ecs-cluster \
  -c stackName=my-cluster \
  -c vpcStackName=my-vpc \
  -c createExternalLoadBalancer=true \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=ecs-cluster \
  -c environment=dev

What auto-imports:

  • All VPC details (VPC ID, CIDR, subnets, AZs) from VPC stack exports
  • Public subnets auto-imported for external ALB when createExternalLoadBalancer=true

Cluster with Both ALBs

pnpm exec cdk synth \
  -c stackType=ecs-cluster \
  -c stackName=production-cluster \
  -c vpcStackName=production-vpc \
  -c createExternalLoadBalancer=true \
  -c createInternalLoadBalancer=true \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
  -c ownerTag=Platform \
  -c productTag=spicy \
  -c componentTag=ecs-cluster \
  -c environment=prod

Cluster with Spot Instances

pnpm exec cdk synth \
  -c stackType=ecs-cluster \
  -c stackName=dev-cluster \
  -c vpcStackName=dev-vpc \
  -c spotEnabled=true \
  -c onDemandPercentage=20 \
  -c spotAllocationStrategy=capacity-optimized \
  -c instanceType=m5a.large \
  -c minClusterSize=2 \
  -c maxClusterSize=4 \
  -c ownerTag=Dev \
  -c productTag=myapp \
  -c componentTag=ecs-cluster \
  -c environment=dev

Cluster with Fargate Enabled

pnpm exec cdk synth \
  -c stackType=ecs-cluster \
  -c stackName=hybrid-cluster \
  -c vpcStackName=my-vpc \
  -c enableFargate=true \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=ecs-cluster \
  -c environment=dev


ALB Stack (Persistent bg-common ALB)

The ALB stack creates a persistent Application Load Balancer that can be shared across multiple service deployments. This is the "bg-common" pattern where the ALB persists even when service stacks are deleted and recreated.

Important: The ALB stack must be deployed before service stacks that reference it. The ALB ARN and listener ARNs from the ALB stack outputs are required as inputs to the service stack.

Internet-Facing ALB

pnpm exec cdk synth \
  -c stackType=alb \
  -c stackName=my-service-alb \
  -c vpcId=vpc-12345678 \
  -c vpcCidrBlock=10.0.0.0/16 \
  -c availabilityZones=ca-central-1a,ca-central-1b \
  -c subnetIds=subnet-xxx,subnet-yyy \
  -c scheme=internet-facing \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
  -c logsBucketName=my-cluster-logs \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=alb \
  -c environment=dev

Creates:

  • Application Load Balancer (internet-facing)
  • Security Group for ALB
  • HTTP Listener (port 80)
  • HTTPS Listener (port 443) with SSL certificate
  • HTTP→HTTPS redirect rule
  • ALB access logs to S3 (if logsBucketName provided)

Internal ALB

pnpm exec cdk synth \
  -c stackType=alb \
  -c stackName=my-service-alb \
  -c vpcId=vpc-12345678 \
  -c vpcCidrBlock=10.0.0.0/16 \
  -c availabilityZones=ca-central-1a,ca-central-1b \
  -c subnetIds=subnet-aaa,subnet-bbb \
  -c scheme=internal \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
  -c logsBucketName=my-cluster-logs \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=alb \
  -c environment=dev

ALB with Blue/Green DNS

pnpm exec cdk synth \
  -c stackType=alb \
  -c stackName=my-service-alb \
  -c vpcId=vpc-12345678 \
  -c vpcCidrBlock=10.0.0.0/16 \
  -c availabilityZones=ca-central-1a,ca-central-1b \
  -c subnetIds=subnet-xxx,subnet-yyy \
  -c scheme=internet-facing \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
  -c logsBucketName=my-cluster-logs \
  -c activeHostname=api.example.com \
  -c inactiveHostname=inactive-api.example.com \
  -c bgHostedZoneId=Z1234567890 \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=alb \
  -c environment=prod

Creates:

  • All resources from internet-facing ALB example
  • Route53 A record for active hostname (api.example.com)
  • Route53 A record for inactive hostname (inactive-api.example.com)

ECS Service Stack

Minimal Service (No Load Balancer) - Using CloudFormation Imports

Minimal props approach: Only clusterName is required. VPC info auto-imports from cluster stack exports.

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=dev

What auto-imports:

  • VPC stack name from ${clusterStackName}-VPCStackName (cluster stack export)
  • VPC ID from ${clusterStackName}-VPC (cluster stack export)
  • VPC CIDR from ${vpcStackName}-VPCCIDR (VPC stack export, using imported VPC stack name)
  • Private subnets from ${vpcStackName}-PrivateSubnetA1ID, etc. (VPC stack exports)
  • Number of AZs from ${vpcStackName}-NumberOfAZs (VPC stack export)
  • Logs bucket from ${clusterStackName}-logs-s3-bucket (cluster stack export)

Note: The service stack imports the VPC stack name from the cluster stack export ${clusterStackName}-VPCStackName, then uses that to import all VPC details (CIDR, subnets, AZs) from the VPC stack exports.

Service with Cluster ALB - Using CloudFormation Imports

Minimal props: Listener ARN auto-imports from cluster stack.

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c hostHeader=api.example.com \
  -c priority=100 \
  -c useExternalALB=true \
  -c healthCheckPath=/health \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=dev

What auto-imports:

  • VPC stack name from ${clusterStackName}-VPCStackName (cluster stack export)
  • VPC ID from ${clusterStackName}-VPC (cluster stack export)
  • VPC CIDR from ${vpcStackName}-VPCCIDR (VPC stack export, using imported VPC stack name)
  • Private subnets from ${vpcStackName}-PrivateSubnetA1ID, etc. (VPC stack exports)
  • Number of AZs from ${vpcStackName}-NumberOfAZs (VPC stack export)
  • External listener ARN from ${clusterStackName}-internet-facing-https-listener (when useExternalALB=true)
  • Internal listener ARN from ${clusterStackName}-internal-https-listener (when useInternalALB=true)

Explicit listener ARN (backward compatible):

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c vpcId=vpc-12345678 \
  -c vpcCidrBlock=10.0.0.0/16 \
  -c availabilityZones=ca-central-1a,ca-central-1b \
  -c privateSubnetIds=subnet-aaa,subnet-bbb \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c hostHeader=api.example.com \
  -c priority=100 \
  -c useExternalALB=true \
  -c externalListenerArn=arn:aws:elasticloadbalancing:ca-central-1:123456789:listener/app/test-alb/1234567890123456/1234567890123456 \
  -c healthCheckPath=/health \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=dev

Service with bg-common ALB (Default - useClusterAlb=false)

Important: When using bg-common ALB, the ALB and Route53 DNS records are created in a separate ALB stack, not in the service stack. The service stack only creates listener rules on the existing ALB.

Step 1: Deploy ALB Stack (creates ALB and Route53 records)

pnpm exec cdk synth \
  -c stackType=alb \
  -c stackName=my-service-alb \
  -c vpcId=vpc-12345678 \
  -c vpcCidrBlock=10.0.0.0/16 \
  -c availabilityZones=ca-central-1a,ca-central-1b \
  -c subnetIds=subnet-xxx,subnet-yyy \
  -c scheme=internet-facing \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
  -c logsBucketName=my-cluster-logs \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=alb \
  -c environment=dev

Step 2: Deploy Service Stack (creates listener rules on existing ALB)

Minimal props using CloudFormation imports:

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c albStackName=my-service-alb \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c healthCheckPath=/health \
  -c hostHeader=api.example.com \
  -c priority=100 \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=dev

What auto-imports:

  • VPC ID from ${clusterName}-VPC (cluster stack export)
  • VPC CIDR from ${clusterName}-VPCCIDR (cluster stack export)
  • Private subnets from ${clusterName}-PrivateSubnetA1ID, etc. (cluster stack exports)
  • Availability zones from ${clusterName}-AvailabilityZones (cluster stack export)
  • ALB ARN from ${albStackName}-internet-facing-arn (ALB stack export)
  • HTTPS listener ARN from ${albStackName}-internet-facing-https-listener (ALB stack export)
  • HTTP listener ARN from ${albStackName}-internet-facing-http-listener (ALB stack export)
  • Logs bucket from ${clusterName}-logs-s3-bucket (cluster stack export)

Note: vpcStackName is optional - the service stack prefers importing from the cluster stack. The ALB stack name (albStackName) is automatically derived from the service stackName (e.g., my-service-devmy-service-dev-alb).

Note: The service creates listener rules on both HTTP and HTTPS listeners (like the old template), so both listeners are imported if available.

Note: In the Jenkins pipeline, the ALB stack is created automatically. For manual CDK synth, you must deploy the ALB stack first and use albStackName for auto-imports.

Service with bg-common ALB (Internal)

Step 1: Deploy Internal ALB Stack

pnpm exec cdk synth \
  -c stackType=alb \
  -c stackName=my-service-alb \
  -c vpcId=vpc-12345678 \
  -c vpcCidrBlock=10.0.0.0/16 \
  -c availabilityZones=ca-central-1a,ca-central-1b \
  -c subnetIds=subnet-aaa,subnet-bbb \
  -c scheme=internal \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
  -c logsBucketName=my-cluster-logs \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=alb \
  -c environment=dev

Step 2: Deploy Service Stack

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c albStackName=my-service-alb \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c healthCheckPath=/health \
  -c hostHeader=api-internal.example.com \
  -c priority=100 \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=dev

Service with bg-common ALB + Blue/Green Deployment

Note: Blue/green deployments are handled automatically by the Jenkins pipeline. The pipeline:

  1. Automatically creates the bg-common ALB stack (if it doesn't exist)
  2. Deploys to the inactive color (blue or green)
  3. Runs blueGreenTest against the inactive hostname
  4. Waits for deployment to stabilize
  5. Swaps hostnames to make the new deployment active
  6. Runs smokeTest against the new active hostname
  7. Keeps the old stack for rollback window (default 2 hours)

For manual CDK synth, deploy both stacks:

Step 1: Deploy ALB Stack with Blue/Green DNS

pnpm exec cdk synth \
  -c stackType=alb \
  -c stackName=my-service-alb \
  -c vpcId=vpc-12345678 \
  -c vpcCidrBlock=10.0.0.0/16 \
  -c availabilityZones=ca-central-1a,ca-central-1b \
  -c subnetIds=subnet-xxx,subnet-yyy \
  -c scheme=internet-facing \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/xxx \
  -c logsBucketName=my-cluster-logs \
  -c activeHostname=api.example.com \
  -c inactiveHostname=inactive-api.example.com \
  -c bgHostedZoneId=Z1234567890 \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=alb \
  -c environment=prod

Step 2: Deploy Service Stack (Blue or Green)

After deploying the ALB stack, use albStackName for auto-imports:

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service-blue \
  -c clusterName=my-cluster \
  -c albStackName=my-service-alb \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c activeHostname=api.example.com \
  -c inactiveHostname=inactive-api.example.com \
  -c bgHostedZoneId=Z1234567890 \
  -c healthCheckPath=/health \
  -c hostHeader=api.example.com \
  -c priority=100 \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=prod

How blue/green works:

  1. ALB stack creates DNS records for both active and inactive hostnames (both point to the same ALB)
  2. Pipeline determines current active color from SSM Parameter Store (defaults to 'blue')
  3. Pipeline deploys new version to inactive color (green if blue is active, blue if green is active)
  4. Pipeline runs blueGreenTest hook against inactive hostname
  5. Pipeline swaps hostnames by updating listener rule priorities
  6. Pipeline runs smokeTest hook against new active hostname
  7. Old stack is retained for rollback window (configurable via rollbackWindowHours)
  8. Rollback is instant - just swaps hostnames back (no new deployment needed)

Service with Auto-Scaling

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c desiredCount=2 \
  -c minCapacity=2 \
  -c maxCapacity=20 \
  -c targetCpuUtilization=70 \
  -c targetMemoryUtilization=80 \
  -c targetRequestsPerTarget=1000 \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=dev

Service with Environment Variables and Secrets

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c environment_vars='{"NODE_ENV":"production","LOG_LEVEL":"info"}' \
  -c secrets='{"DATABASE_URL":"arn:aws:secretsmanager:ca-central-1:123456789:secret:db-url","API_KEY":"arn:aws:secretsmanager:ca-central-1:123456789:secret:api-key"}' \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=dev

Service with Mixed Capacity Strategy (EC2 + Fargate)

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=80 \
  -c cpu=512 \
  -c memory=1024 \
  -c capacityProviderStrategy='[{"capacityProvider":"my-cluster-ec2","base":2,"weight":3},{"capacityProvider":"FARGATE_SPOT","weight":1}]' \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=dev

Service with Custom Resource Limits

pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=my-service \
  -c clusterName=my-cluster \
  -c serviceName=my-service \
  -c image=nginx:latest \
  -c containerPort=3000 \
  -c cpu=1024 \
  -c memory=2048 \
  -c desiredCount=4 \
  -c ownerTag=MyTeam \
  -c productTag=myproduct \
  -c componentTag=service \
  -c environment=prod

Complete Production Example

Full Stack Deployment Sequence

# 1. Deploy VPC
pnpm exec cdk synth \
  -c stackType=vpc \
  -c stackName=production-vpc \
  -c ownerTag=Platform \
  -c productTag=spicy \
  -c componentTag=vpc \
  -c numberOfAzs=4 \
  -c availabilityZones=ca-central-1a,ca-central-1b,ca-central-1c,ca-central-1d

# 2. Deploy ECS Cluster
pnpm exec cdk synth \
  -c stackType=ecs-cluster \
  -c stackName=production-cluster \
  -c vpcStackName=production-vpc \
  -c createExternalLoadBalancer=true \
  -c createInternalLoadBalancer=true \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/prod \
  -c instanceType=m5a.xlarge \
  -c minClusterSize=3 \
  -c maxClusterSize=10 \
  -c spotEnabled=true \
  -c onDemandPercentage=50 \
  -c ownerTag=Platform \
  -c productTag=spicy \
  -c componentTag=ecs-cluster \
  -c environment=prod

# 3. Deploy ALB Stack (bg-common pattern)
pnpm exec cdk synth \
  -c stackType=alb \
  -c stackName=api-service-prod-alb \
  -c vpcStackName=production-vpc \
  -c numberOfAzs=3 \
  -c scheme=internet-facing \
  -c certificateArn=arn:aws:acm:ca-central-1:123456789:certificate/prod \
  -c logsBucketName=production-cluster-ca-central-1-alb-logs \
  -c activeHostname=api.example.com \
  -c inactiveHostname=inactive-api.example.com \
  -c bgHostedZoneId=Z1234567890 \
  -c ownerTag=Platform \
  -c productTag=spicy \
  -c componentTag=alb \
  -c environment=prod

# 4. Deploy Service Stack (references existing ALB via albStackName)
pnpm exec cdk synth \
  -c stackType=ecs-service \
  -c stackName=api-service-prod-blue \
  -c clusterName=production-cluster \
  -c albStackName=api-service-prod-alb \
  -c serviceName=api-service \
  -c image=nexus.kodeniks.com/docker-hosted/api-service:v1.2.3 \
  -c containerPort=3000 \
  -c cpu=512 \
  -c memory=1024 \
  -c activeHostname=api.example.com \
  -c inactiveHostname=inactive-api.example.com \
  -c bgHostedZoneId=Z1234567890 \
  -c desiredCount=4 \
  -c minCapacity=4 \
  -c maxCapacity=24 \
  -c targetCpuUtilization=70 \
  -c targetMemoryUtilization=80 \
  -c targetRequestsPerTarget=1000 \
  -c healthCheckPath=/health \
  -c hostHeader=api.example.com \
  -c priority=100 \
  -c ownerTag=Platform \
  -c productTag=spicy \
  -c componentTag=api \
  -c environment=prod

Parameter Reference

Common Parameters (All Stacks)

Parameter Required Description Example
stackType Stack type: vpc, ecs-cluster, ecs-service vpc
stackName CloudFormation stack name my-vpc
ownerTag Owner tag MyTeam
productTag Product tag myproduct
componentTag Component tag vpc
environment ⚠️ Environment (required for cluster/service) dev

VPC-Specific Parameters

Parameter Default Description
vpcCidr 172.1.0.0/16 VPC CIDR block
numberOfAzs 4 Number of AZs (2, 3, or 4)
availabilityZones auto Comma-separated AZ list
createPrivateSubnets true Create private subnets
createAdditionalPrivateSubnets true Create NACL-protected subnets

ECS Cluster Parameters

Parameter Default Description
vpcStackName Required VPC stack name - all VPC details (VPC ID, CIDR, subnets, AZs) auto-import from VPC stack exports
instanceType m5a.large EC2 instance type
minClusterSize 2 Minimum instances
maxClusterSize 4 Maximum instances
createExternalLoadBalancer false Create external ALB (public subnets auto-imported from VPC stack if enabled)
createInternalLoadBalancer false Create internal ALB
certificateArn - SSL certificate ARN
spotEnabled false Enable Spot instances
onDemandPercentage 100 On-demand percentage
enableFargate false Enable Fargate providers

ALB Stack Parameters

Parameter Default Description
vpcId - VPC ID (required, or use vpcStackName to import)
vpcCidrBlock - VPC CIDR (required, or use vpcStackName to import)
availabilityZones - Comma-separated AZs (required, or use vpcStackName + numberOfAzs to import)
subnetIds - Subnet IDs for ALB (required)
scheme internet-facing ALB scheme: internet-facing or internal
certificateArn - SSL certificate ARN (required for HTTPS)
logsBucketName - S3 bucket name for ALB access logs
logsPrefix - S3 prefix for ALB access logs
idleTimeout 60 ALB idle timeout in seconds
redirectHttpToHttps true Enable HTTP→HTTPS redirect
hostName - Simple hostname (e.g., api.example.com) - auto-generates active/inactive hostnames (like old HostName)
activeHostname - Active hostname for blue/green DNS
inactiveHostname - Inactive hostname for blue/green DNS
bgHostedZoneId - Route53 hosted zone ID for blue/green DNS (optional - only needed if creating DNS records)

ECS Service Parameters

Parameter Default Description
clusterName - ECS cluster name (required) - used to auto-import VPC stack name, VPC ID, and listener ARNs from cluster stack exports
clusterStackName - Cluster stack name (defaults to clusterName) - used for CloudFormation imports
vpcStackName - VPC stack name (auto-imported from ${clusterStackName}-VPCStackName, or provide explicitly)
albStackName - ALB stack name (for bg-common ALB) - used to auto-import ALB ARN and listener ARNs from ALB stack
vpcId - VPC ID (required, or auto-imported from ${clusterStackName}-VPC)
vpcCidrBlock - VPC CIDR (required, or auto-imported from ${vpcStackName}-VPCCIDR using imported VPC stack name)
availabilityZones - Comma-separated AZs (required, or auto-derived from VPC stack imports with numberOfAzs)
privateSubnetIds - Private subnet IDs (required, or auto-imported from ${vpcStackName}-PrivateSubnetA1ID, etc.)
numberOfAzs - Number of AZs (required when importing subnets from VPC stack)
publicSubnetIds - Public subnet IDs (for individual ALB)
serviceName - Service name (required)
image - Docker image URI (required)
containerPort 3000 Container port
cpu 256 CPU units
memory 512 Memory in MiB
desiredCount 2 Desired task count

bg-common ALB Parameters (useClusterAlb=false)

When useClusterAlb is false or not set, the service uses a bg-common ALB from a separate ALB stack. These parameters reference the existing ALB:

Minimal props using CloudFormation imports:

  • Provide albStackName to auto-import ALB ARN and listener ARNs
  • Service creates listener rules on both HTTP and HTTPS listeners (like the old template)
Parameter Default Description
albStackName - ALB stack name - used to auto-import ALB ARN and listener ARNs (preferred)
existingAlbArn - ALB ARN (auto-imported from ${albStackName}-internet-facing-arn, or provide explicitly)
existingAlbHttpsListenerArn - HTTPS listener ARN (auto-imported from ${albStackName}-internet-facing-https-listener, or provide explicitly)
existingAlbHttpListenerArn - HTTP listener ARN (auto-imported from ${albStackName}-internet-facing-http-listener, or provide explicitly)
hostHeader - Host header for listener rule
priority 100 Listener rule priority (lower = higher precedence)
stickiness true Enable session stickiness on target group
stickinessDuration 86400 Session stickiness duration in seconds
deregistrationDelay 60 Target deregistration delay in seconds
hostName - Simple hostname (e.g., api.example.com) - auto-generates active/inactive (like old HostName)
activeHostname - Active hostname for blue/green DNS (must match ALB stack config)
inactiveHostname - Inactive hostname for blue/green DNS (must match ALB stack config)
bgHostedZoneId - Route53 hosted zone ID (optional - only needed if creating DNS records)

Blue/Green Deployment Parameters

Parameter Default Description
blueGreen false Enable blue/green deployment (pipeline handles color switching automatically)
hostName - Simple hostname (e.g., api.example.com) - auto-generates activeHostname and inactiveHostname (like old HostName)
activeHostname - Active hostname (e.g., api.example.com) - used by bg-common ALB stack to create DNS records
inactiveHostname - Inactive hostname (e.g., inactive-api.example.com) - used by bg-common ALB stack to create DNS records
bgHostedZoneId - Route53 hosted zone ID (for bg-common ALB stack DNS records) - required for DNS records (hostnames are optional)
rollbackWindowHours 2 Hours to keep old stack for rollback (default 2 hours)

Note: Hostnames are optional. Services without hostnames (pub/sub workers, background jobs) don't need DNS records. Only provide bgHostedZoneId if you want Route53 DNS records created.

Cloudflare DNS Delegation: If using Cloudflare for DNS, you can delegate a subdomain to AWS Route53:

  1. Create a Route53 hosted zone for the subdomain (e.g., production.mydomain.com)
  2. Point Cloudflare NS records for *.production.mydomain.com to the Route53 name servers
  3. Use the Route53 hosted zone ID as bgHostedZoneId
  4. AWS will manage DNS records for all services under that subdomain

Cluster ALB Parameters (useClusterAlb=true)

When useClusterAlb is true, the service uses the cluster's shared ALB:

Minimal props using CloudFormation imports:

  • Provide clusterName to auto-import listener ARNs from cluster stack exports
  • Listener ARNs auto-import from ${clusterName}-internet-facing-https-listener or ${clusterName}-internal-https-listener
Parameter Default Description
useClusterAlb false Set to true to use cluster ALB
hostHeader - Host header for routing
pathPatterns - Comma-separated path patterns
priority 100 Listener rule priority
useExternalALB false Use external cluster ALB (auto-imports listener ARN from ${clusterName}-internet-facing-https-listener)
useInternalALB false Use internal cluster ALB (auto-imports listener ARN from ${clusterName}-internal-https-listener)
externalListenerArn - External listener ARN (auto-imported when useExternalALB=true, or provide explicitly)
internalListenerArn - Internal listener ARN (auto-imported when useInternalALB=true, or provide explicitly)

Auto-Scaling Parameters

Parameter Default Description
minCapacity - Minimum tasks
maxCapacity - Maximum tasks
targetCpuUtilization - Target CPU % (0-100)
targetMemoryUtilization - Target memory % (0-100)
targetRequestsPerTarget - Target requests per task per minute

Testing Checklist

When running synth commands, verify:

  • No errors (only expected warnings)
  • Resources are created as expected
  • Logical IDs match expected patterns
  • Tags are applied correctly
  • Outputs are exported correctly

Troubleshooting

Common Issues

  1. Missing required parameters: Check error message for missing context values
  2. Invalid subnet IDs: Ensure subnet IDs exist in the specified VPC
  3. Certificate ARN format: Must be valid ACM certificate ARN
  4. JSON parsing errors: Ensure JSON strings are properly escaped/quoted

Getting Stack Outputs

After deployment, get stack outputs:

aws cloudformation describe-stacks \
  --stack-name my-stack \
  --query 'Stacks[0].Outputs' \
  --output table

Quick Reference

Minimal Commands

VPC:

pnpm exec cdk synth -c stackType=vpc -c stackName=test -c ownerTag=T -c productTag=t -c componentTag=v

Cluster:

pnpm exec cdk synth -c stackType=ecs-cluster -c stackName=test -c vpcStackName=test-vpc -c ownerTag=T -c productTag=t -c componentTag=c -c environment=d

Service:

pnpm exec cdk synth -c stackType=ecs-service -c stackName=test -c clusterName=test -c vpcId=vpc-123 -c vpcCidrBlock=10.0.0.0/16 -c availabilityZones=ca-central-1a -c privateSubnetIds=subnet-123 -c serviceName=test -c image=nginx:latest -c containerPort=80 -c ownerTag=T -c productTag=t -c componentTag=s -c environment=d