Building ALB + Fargate with CDK
Published on
April 27, 2025
Updated on
April 27, 2025
27
min read
AWS
In modern service architectures, containerization and serverless patterns can greatly simplify operational work. This example shows how to deploy a serverless service on Fargate using AWS CDK (v2).
1. Project Structure Overview
Before you begin, make sure you have installed AWS CDK and initialized a TypeScript project. The directory layout might look like this:
my-cdk-project/
├── bin/
│ └── my-cdk-project.ts // CDK app entry point
├── lib/
│ └── server-stack.ts // The Stack we'll write next
├── package.json
└── tsconfig.json
2. Core CDK Code
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as log from 'aws-cdk-lib/aws-logs';
import { Construct } from 'constructs';
export class ServerStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// 1. Import existing VPC and Security Groups
const albSg = ec2.SecurityGroup.fromSecurityGroupId(this, 'alb-sg', 'alb-sg-id');
const ecsSg = ec2.SecurityGroup.fromSecurityGroupId(this, 'ecs-sg', 'ecs-sg-id');
const vpc = ec2.Vpc.fromLookup(this, 'vpc', { vpcId: 'vpc-id' });
const albSubnetA = ec2.Subnet.fromSubnetId(this, 'alb-subnet-a', 'alb-subnet-a-id');
const albSubnetC = ec2.Subnet.fromSubnetId(this, 'alb-subnet-c', 'alb-subnet-c-id');
const ecsSubnetA = ec2.Subnet.fromSubnetId(this, 'ecs-subnet-a', 'ecs-subnet-a-id');
const ecsSubnetC = ec2.Subnet.fromSubnetId(this, 'ecs-subnet-c', 'ecs-subnet-c-id');
// 2. Import existing IAM roles
const taskRole = iam.Role.fromRoleArn(this, 'task-role', 'task-role-arn', {
mutable: false,
});
const taskExecutionRole = iam.Role.fromRoleArn(this, 'task-execution-role', 'task-execution-role-arn', {
mutable: false,
});
// 3. Create an ECS cluster
const cluster = new ecs.Cluster(this, 'ecs-cluster', {
vpc: vpc,
clusterName: 'ecs-cluster',
});
// 4. Define the Fargate task
const taskDefinition = new ecs.FargateTaskDefinition(this, 'task-definition', {
taskRole: taskRole,
executionRole: taskExecutionRole,
cpu: 512,
memoryLimitMiB: 1024,
});
// 5. Import the ECR repository
const repository = ecr.Repository.fromRepositoryArn(this, 'ecr-repository', 'ecr-arn');
// 6. Create a CloudWatch Logs log group
const logGroup = new log.LogGroup(this, 'server-log', {
logGroupName: '/aws/server-log',
retention: log.RetentionDays.THREE_YEARS,
});
// 7. Configure the container image
taskDefinition.addContainer('task', {
image: ecs.ContainerImage.fromEcrRepository(repository, 'xxxxx'),
logging: ecs.LogDriver.awsLogs({
logGroup: logGroup,
streamPrefix: 'api',
}),
portMappings: [{ containerPort: 80 }],
environment: {
NODE_ENV: 'it',
},
readonlyRootFilesystem: true,
});
// 8. Deploy the Fargate service
const service = new ecs.FargateService(this, 'service', {
serviceName: 'server_name',
cluster: cluster,
taskDefinition: taskDefinition,
securityGroups: [ecsSg],
vpcSubnets: { subnets: [ecsSubnetA, ecsSubnetC] },
});
// 9. Create an Application Load Balancer (ALB)
const alb = new elbv2.ApplicationLoadBalancer(this, `alb`, {
loadBalancerName: 'alb',
securityGroup: albSg,
vpc: vpc,
vpcSubnets: { subnets: [albSubnetA, albSubnetC] },
});
// 10. Create a Target Group with health checks
const targetGroup = new elbv2.ApplicationTargetGroup(this, 'alb-tg', {
targetGroupName: '',
vpc: vpc,
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
targetType: elbv2.TargetType.IP,
crossZoneEnabled: true,
healthCheck: {
path: '/health',
healthyHttpCodes: '200',
},
targets: [service],
});
// 11. Add a listener and routing rule
const listener = alb.addListener('alb-listener', {
port: 80,
protocol: elbv2.ApplicationProtocol.HTTP,
open: false,
defaultAction: elbv2.ListenerAction.fixedResponse(404, { contentType: 'application/json' }),
});
new elbv2.CfnListenerRule(this, 'server-rule', {
listenerArn: listener.listenerArn,
priority: 1,
conditions: [
{
field: 'path-pattern',
values: ['/api/*'], // Only forward requests matching this path to Fargate
},
],
actions: [{ type: 'forward', targetGroupArn: targetGroup.targetGroupArn }],
});
}
}
3. Conclusion
With this example, you can quickly reuse existing VPCs, subnets, security groups, IAM roles, ECR repositories, and log groups, and deploy an entire stack with a single CDK command:
- High Availability: Multi-AZ load balancing + serverless Fargate
- Security: Fine-grained security groups, read-only container filesystem
- Observability: Real-time logging with CloudWatch Logs
You can extend this template to support HTTPS, auto-scaling, blue-green deployments, and other production-grade features. Feel free to share your optimizations in the comments!