# CDK Synth Command Examples This document provides comprehensive examples of `pnpm exec cdk synth` commands for all stack types and configurations. ## Prerequisites ```bash 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) ```bash 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 ```bash 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) ```bash 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) ```bash 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 required props:** `vpcStackName` and tags (`ownerTag`, `productTag`). All VPC details auto-import from VPC stack exports, including `numberOfAzs`. ```bash 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` (or override with `-c numberOfAzs=N`) - 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:** ```bash 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 ```bash 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 ```bash 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 ```bash 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 **Uses CloudFormation imports:** Only `clusterName` (or `clusterStackName`) and `numberOfAzs` required. VPC details and subnets are auto-imported from cluster/VPC stack exports. `numberOfAzs` is also auto-imported from the VPC stack if not provided. ```bash pnpm exec cdk synth \ -c stackType=alb \ -c stackName=my-service-alb \ -c clusterName=my-cluster \ -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 ``` **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) - Number of AZs from `${vpcStackName}-NumberOfAZs` (VPC stack export) - Public subnet IDs from `${vpcStackName}-PublicSubnetAID`, etc. (for internet-facing) - Private subnet IDs from `${vpcStackName}-PrivateSubnetA1ID`, etc. (for internal) **Creates:** - Application Load Balancer (internet-facing) - Security Group for ALB - HTTP Listener (port 80) with HTTP→HTTPS redirect - HTTPS Listener (port 443) with SSL certificate - ALB access logs to S3 (if logsBucketName provided) ### Internal ALB ```bash pnpm exec cdk synth \ -c stackType=alb \ -c stackName=my-service-alb \ -c clusterName=my-cluster \ -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 ```bash pnpm exec cdk synth \ -c stackType=alb \ -c stackName=my-service-alb \ -c clusterName=my-cluster \ -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 required props:** `clusterName`, `serviceName`, `image`, `containerPort`, and tags (`ownerTag`, `productTag`). All VPC info auto-imports from cluster/VPC stack exports. ```bash 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) - Number of AZs from `${vpcStackName}-NumberOfAZs` (VPC stack export) - Private subnets from `${vpcStackName}-PrivateSubnetA1ID`, etc. (VPC stack exports) - 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. `numberOfAzs` can be provided explicitly via `-c numberOfAzs=N` but will auto-import from the VPC stack if omitted. ### Service with Cluster ALB - Using CloudFormation Imports **Minimal props:** Listener ARN auto-imports from cluster stack. ```bash 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):** ```bash 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) ```bash 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:** ```bash 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-dev` → `my-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** ```bash 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** ```bash 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** ```bash 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: ```bash 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 ```bash 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 ```bash 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) ```bash 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 ```bash 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 ```bash # 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` | auto | Number of AZs (auto-imported from VPC stack, or override explicitly) | | `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: ```bash aws cloudformation describe-stacks \ --stack-name my-stack \ --query 'Stacks[0].Outputs' \ --output table ``` --- ## Quick Reference ### Minimal Commands **VPC:** ```bash pnpm exec cdk synth -c stackType=vpc -c stackName=test -c ownerTag=T -c productTag=t -c componentTag=v ``` **Cluster:** ```bash 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 (using CloudFormation imports):** ```bash pnpm exec cdk synth -c stackType=ecs-service -c stackName=test -c clusterName=test -c serviceName=test -c image=nginx:latest -c containerPort=80 -c ownerTag=T -c productTag=t -c componentTag=s -c environment=d ```