AWS Builder Center

Bun JavaScript container that uses AWS SDK to connect to DynamoDB

Build a Bun JavaScript container that runs in AWS Fargate via Amazon ECS, and uses AWS SDK to query a DynamoDB table

About

Bun  is a fast, lightweight server side JavaScript implementation that
is based on Apple's JavaScriptCore instead of Google's V8.
In this pattern you will learn how to create a container that hosts a Bun app,
and deploy the container to AWS Fargate using Amazon ECS.
The containerized application will use the AWS SDK to interact with DynamoDB table.
The sample application is a basic hit counter that will count the number of requests
that it receives, and return the grand total.

Architecture

The following architecture will be deployed to your AWS account:
  1. The application is packaged up as a container image that has the Bun runtime,
    application code, and the JavaScript AWS SDK.
  2. Amazon ECS orchestrates a scalable replica set of containers running on AWS Fargate
  3. Traffic ingress from the public arrives at the container via an Application Load Balancer
  4. The container running inside of AWS Fargate uses the JavaScript AWS SDK, and an
    automatically vended AWS Identity and Access Management (IAM) Role.
  5. Each time a request arrives at the container, it makes a database query to increment
    the hit counter, and return the result.

Dependencies

This pattern requires the following local dependencies:

Build the application

Download the following files to define a simple JavaScript application:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// File: index.ts
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, UpdateCommand, UpdateCommandOutput } from "@aws-sdk/lib-dynamodb";

// Bare-bones DynamoDB Client
const dynamodb = new DynamoDBClient();

// Bare-bones document client
const db = DynamoDBDocumentClient.from(dynamodb);

const TABLE_NAME = process.env.TABLE_NAME;

if (!TABLE_NAME) {
throw new Error('Expected environment variable `TABLE_NAME` with name of DynamoDB table to store counter in');
}

const server = Bun.serve({
port: 3000,
async fetch(req) {
const command = new UpdateCommand({
TableName: TABLE_NAME,
UpdateExpression: "ADD hitCount :increment",
ExpressionAttributeValues: {
':increment': 1
},
Key: {
counter: 'global'
},
ReturnValues: "ALL_NEW"
});

const resp = await db.send(command) as UpdateCommandOutput;

if (resp.Attributes) {
return new Response(resp.Attributes.hitCount);
} else {
return new Response(JSON.stringify(resp));
}
},
});

console.log(`Listening on http://localhost:${server.port} ...`);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// File: index.ts
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, UpdateCommand, UpdateCommandOutput } from "@aws-sdk/lib-dynamodb";

// Bare-bones DynamoDB Client
const dynamodb = new DynamoDBClient();

// Bare-bones document client
const db = DynamoDBDocumentClient.from(dynamodb);

const TABLE_NAME = process.env.TABLE_NAME;

if (!TABLE_NAME) {
throw new Error('Expected environment variable `TABLE_NAME` with name of DynamoDB table to store counter in');
}

const server = Bun.serve({
port: 3000,
async fetch(req) {
const command = new UpdateCommand({
TableName: TABLE_NAME,
UpdateExpression: "ADD hitCount :increment",
ExpressionAttributeValues: {
':increment': 1
},
Key: {
counter: 'global'
},
ReturnValues: "ALL_NEW"
});

const resp = await db.send(command) as UpdateCommandOutput;

if (resp.Attributes) {
return new Response(resp.Attributes.hitCount);
} else {
return new Response(JSON.stringify(resp));
}
},
});

console.log(`Listening on http://localhost:${server.port} ...`);
The files above serve the following purpose:
  • package.json - Defines some third party open source packages to install. In specific,
    this installs AWS SDK dependencies for connecting to DynamoDB
  • index.ts - A small JavaScript application that implements a hit counter that increments
    in DynamoDB each time a request arrives.
Now we can use the following Dockerfile to describe how to package this application up
and run it with the Bun JavaScript runtime:
1
2
3
4
5
6
7
8
9
10
11
12
13
# File: Dockerfile
FROM oven/bun
WORKDIR /srv

# Add the package manifest and install packages
ADD package.json .
RUN bun install

# Add the application code
ADD index.ts .

# Specify the command to run when launching the container
CMD bun index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
# File: Dockerfile
FROM oven/bun
WORKDIR /srv

# Add the package manifest and install packages
ADD package.json .
RUN bun install

# Add the application code
ADD index.ts .

# Specify the command to run when launching the container
CMD bun index.ts
Build and push the application container image to a private Amazon ECR container registry
using the following commands:
1
2
3
4
5
6
REPO_URI=$(aws ecr create-repository --repository-name sample-app-repo --query 'repository.repositoryUri' --output text)
if [ -z "${REPO_URI}" ]; then
REPO_URI=$(aws ecr describe-repositories --repository-names sample-app-repo --query 'repositories[0].repositoryUri' --output text)
fi
docker build -t ${REPO_URI}:bun-app .
docker push ${REPO_URI}:bun-app
You can now open the Amazon ECR console  to verify that the image has been built and uploaded to AWS.

Choose a networking environment

This pattern can be deployed on top of either of the following VPC patterns:
Which one you choose depends on your goals for this deployment. You can choose the low cost VPC to start with and upgrade to the large sized VPC later on if you have additional private services, or private database servers you wish to deploy in the VPC.
If you have any doubts as to which VPC to choose, then go with the "Low cost VPC" option.
Download the vpc.yml file from your chosen pattern, but do not deploy it yet. Deployment will be done later in the process

Define the Amazon ECS cluster

The following AWS CloudFormation template creates a simple Amazon ECS cluster that is setup for serverless usage with AWS Fargate.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File: cluster.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Empty ECS cluster that has no EC2 instances. It is designed
to be used with AWS Fargate serverless capacity

Resources:
## Cluster that keeps track of container deployments
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterSettings:
- Name: containerInsights
Value: enabled

# This is a role which is used within Fargate to allow the Fargate agent
# to download images, and upload logs.
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Condition:
ArnLike:
aws:SourceArn: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:*
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
Path: /
# This role enables basic features of ECS. See reference:
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECSTaskExecutionRolePolicy
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

Outputs:
ClusterName:
Description: The ECS cluster into which to launch resources
Value: !Ref ECSCluster
ECSTaskExecutionRole:
Description: The role used to start up a task
Value: !Ref ECSTaskExecutionRole
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File: cluster.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Empty ECS cluster that has no EC2 instances. It is designed
to be used with AWS Fargate serverless capacity

Resources:
## Cluster that keeps track of container deployments
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterSettings:
- Name: containerInsights
Value: enabled

# This is a role which is used within Fargate to allow the Fargate agent
# to download images, and upload logs.
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Condition:
ArnLike:
aws:SourceArn: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:*
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
Path: /
# This role enables basic features of ECS. See reference:
# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/security-iam-awsmanpol.html#security-iam-awsmanpol-AmazonECSTaskExecutionRolePolicy
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

Outputs:
ClusterName:
Description: The ECS cluster into which to launch resources
Value: !Ref ECSCluster
ECSTaskExecutionRole:
Description: The role used to start up a task
Value: !Ref ECSTaskExecutionRole

Define the service

Now we will define the service itself and it's dependencies.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File: service.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: An example service that deploys in AWS VPC networking mode
on AWS Fargate. Service runs with networking in public
subnets and public IP addresses

Parameters:
VpcId:
Type: String
Description: The VPC that the service is running inside of
PublicSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: List of public subnet ID's to put the load balancer and tasks in
ClusterName:
Type: String
Description: The name of the ECS cluster into which to launch capacity.
ECSTaskExecutionRole:
Type: String
Description: The role used to start up an ECS task
ServiceName:
Type: String
Default: web
Description: A name for the service
ImageUrl:
Type: String
Default: public.ecr.aws/docker/library/nginx:latest
Description: The url of a docker image that contains the application process that
will handle the traffic for this service
ContainerCpu:
Type: Number
Default: 256
Description: How much CPU to give the container. 1024 is 1 CPU
ContainerMemory:
Type: Number
Default: 512
Description: How much memory in megabytes to give the container
ContainerPort:
Type: Number
Default: 3000
Description: What port that the application expects traffic on
DesiredCount:
Type: Number
Default: 2
Description: How many copies of the service task to run

Resources:

# The task definition. This is a simple metadata description of what
# container to run, and what resource requirements it has.
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref ServiceName
Cpu: !Ref ContainerCpu
Memory: !Ref ContainerMemory
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn: !Ref ECSTaskExecutionRole
TaskRoleArn: !Ref TaskRole
ContainerDefinitions:
- Name: !Ref ServiceName
Cpu: !Ref ContainerCpu
Memory: !Ref ContainerMemory
Image: !Ref ImageUrl
Environment:
- Name: TABLE_NAME
Value: !Ref HitCounters
PortMappings:
- ContainerPort: !Ref ContainerPort
HostPort: !Ref ContainerPort
LogConfiguration:
LogDriver: 'awslogs'
Options:
mode: non-blocking
max-buffer-size: 25m
awslogs-group: !Ref LogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: !Ref ServiceName

# The service. The service is a resource which allows you to run multiple
# copies of a type of task, and gather up their logs and metrics, as well
# as monitor the number of running tasks and replace any that have crashed
Service:
Type: AWS::ECS::Service
# Avoid race condition between ECS service creation and associating
# the target group with the LB
DependsOn: PublicLoadBalancerListener
Properties:
ServiceName: !Ref ServiceName
Cluster: !Ref ClusterName
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref ServiceSecurityGroup
Subnets: !Ref PublicSubnetIds
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 75
DesiredCount: !Ref DesiredCount
TaskDefinition: !Ref TaskDefinition
LoadBalancers:
- ContainerName: !Ref ServiceName
ContainerPort: !Ref ContainerPort
TargetGroupArn: !Ref ServiceTargetGroup

# Security group that limits network access
# to the task
ServiceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for service
VpcId: !Ref VpcId

# Keeps track of the list of tasks for the service
ServiceTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 6
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
TargetType: ip
Port: !Ref ContainerPort
Protocol: HTTP
UnhealthyThresholdCount: 10
VpcId: !Ref VpcId
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 0

# A public facing load balancer, this is used as ingress for
# public facing internet traffic.
PublicLoadBalancerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the public facing load balancer
VpcId: !Ref VpcId
SecurityGroupIngress:
# Allow access to public facing ALB from any IP address
- CidrIp: 0.0.0.0/0
IpProtocol: -1
PublicLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: '30'
Subnets: !Ref PublicSubnetIds
SecurityGroups:
- !Ref PublicLoadBalancerSG
PublicLoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: 'forward'
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref ServiceTargetGroup
Weight: 100
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 80
Protocol: HTTP

# Open up the service's security group to traffic originating
# from the security group of the load balancer.
ServiceIngressfromLoadBalancer:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from the public ALB
GroupId: !Ref ServiceSecurityGroup
IpProtocol: -1
SourceSecurityGroupId: !Ref 'PublicLoadBalancerSG'

# The table that will store the hits
HitCounters:
Type: AWS::DynamoDB::Table
Properties:
KeySchema:
- AttributeName: counter
KeyType: HASH
AttributeDefinitions:
- AttributeName: counter
AttributeType: "S"
BillingMode: PAY_PER_REQUEST

# The IAM role that will be used by the running task.
# This grants the task permission to use the hit counter table
TaskRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Condition:
ArnLike:
aws:SourceArn: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:*
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
Policies:
- PolicyName: AccessToHitCounterTable
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:Get*
- dynamodb:UpdateItem
Resource: !GetAtt HitCounters.Arn


# This log group stores the stdout logs from this service's containers
LogGroup:
Type: AWS::Logs::LogGroup
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File: service.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: An example service that deploys in AWS VPC networking mode
on AWS Fargate. Service runs with networking in public
subnets and public IP addresses

Parameters:
VpcId:
Type: String
Description: The VPC that the service is running inside of
PublicSubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: List of public subnet ID's to put the load balancer and tasks in
ClusterName:
Type: String
Description: The name of the ECS cluster into which to launch capacity.
ECSTaskExecutionRole:
Type: String
Description: The role used to start up an ECS task
ServiceName:
Type: String
Default: web
Description: A name for the service
ImageUrl:
Type: String
Default: public.ecr.aws/docker/library/nginx:latest
Description: The url of a docker image that contains the application process that
will handle the traffic for this service
ContainerCpu:
Type: Number
Default: 256
Description: How much CPU to give the container. 1024 is 1 CPU
ContainerMemory:
Type: Number
Default: 512
Description: How much memory in megabytes to give the container
ContainerPort:
Type: Number
Default: 3000
Description: What port that the application expects traffic on
DesiredCount:
Type: Number
Default: 2
Description: How many copies of the service task to run

Resources:

# The task definition. This is a simple metadata description of what
# container to run, and what resource requirements it has.
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref ServiceName
Cpu: !Ref ContainerCpu
Memory: !Ref ContainerMemory
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn: !Ref ECSTaskExecutionRole
TaskRoleArn: !Ref TaskRole
ContainerDefinitions:
- Name: !Ref ServiceName
Cpu: !Ref ContainerCpu
Memory: !Ref ContainerMemory
Image: !Ref ImageUrl
Environment:
- Name: TABLE_NAME
Value: !Ref HitCounters
PortMappings:
- ContainerPort: !Ref ContainerPort
HostPort: !Ref ContainerPort
LogConfiguration:
LogDriver: 'awslogs'
Options:
mode: non-blocking
max-buffer-size: 25m
awslogs-group: !Ref LogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: !Ref ServiceName

# The service. The service is a resource which allows you to run multiple
# copies of a type of task, and gather up their logs and metrics, as well
# as monitor the number of running tasks and replace any that have crashed
Service:
Type: AWS::ECS::Service
# Avoid race condition between ECS service creation and associating
# the target group with the LB
DependsOn: PublicLoadBalancerListener
Properties:
ServiceName: !Ref ServiceName
Cluster: !Ref ClusterName
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref ServiceSecurityGroup
Subnets: !Ref PublicSubnetIds
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 75
DesiredCount: !Ref DesiredCount
TaskDefinition: !Ref TaskDefinition
LoadBalancers:
- ContainerName: !Ref ServiceName
ContainerPort: !Ref ContainerPort
TargetGroupArn: !Ref ServiceTargetGroup

# Security group that limits network access
# to the task
ServiceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for service
VpcId: !Ref VpcId

# Keeps track of the list of tasks for the service
ServiceTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 6
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
TargetType: ip
Port: !Ref ContainerPort
Protocol: HTTP
UnhealthyThresholdCount: 10
VpcId: !Ref VpcId
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: 0

# A public facing load balancer, this is used as ingress for
# public facing internet traffic.
PublicLoadBalancerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the public facing load balancer
VpcId: !Ref VpcId
SecurityGroupIngress:
# Allow access to public facing ALB from any IP address
- CidrIp: 0.0.0.0/0
IpProtocol: -1
PublicLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: '30'
Subnets: !Ref PublicSubnetIds
SecurityGroups:
- !Ref PublicLoadBalancerSG
PublicLoadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: 'forward'
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref ServiceTargetGroup
Weight: 100
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 80
Protocol: HTTP

# Open up the service's security group to traffic originating
# from the security group of the load balancer.
ServiceIngressfromLoadBalancer:
Type: AWS::EC2::SecurityGroupIngress
Properties:
Description: Ingress from the public ALB
GroupId: !Ref ServiceSecurityGroup
IpProtocol: -1
SourceSecurityGroupId: !Ref 'PublicLoadBalancerSG'

# The table that will store the hits
HitCounters:
Type: AWS::DynamoDB::Table
Properties:
KeySchema:
- AttributeName: counter
KeyType: HASH
AttributeDefinitions:
- AttributeName: counter
AttributeType: "S"
BillingMode: PAY_PER_REQUEST

# The IAM role that will be used by the running task.
# This grants the task permission to use the hit counter table
TaskRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Condition:
ArnLike:
aws:SourceArn: !Sub arn:aws:ecs:${AWS::Region}:${AWS::AccountId}:*
StringEquals:
aws:SourceAccount: !Ref AWS::AccountId
Policies:
- PolicyName: AccessToHitCounterTable
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:Get*
- dynamodb:UpdateItem
Resource: !GetAtt HitCounters.Arn


# This log group stores the stdout logs from this service's containers
LogGroup:
Type: AWS::Logs::LogGroup
Some things to note in this template:

Deploy the stack

You can use the following parent.yml with AWS SAM CLI to deploy
all the defined components at once:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File: parent.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Parent stack that deploys VPC, Amazon ECS cluster for AWS Fargate,
and a serverless Amazon ECS service deployment that hosts
the task containers on AWS Fargate
Parameters:
ImageUrl:
Type: String
Description: The url of the container image that you built

Resources:

# The networking configuration. This creates an isolated
# network specific to this particular environment
VpcStack:
Type: AWS::Serverless::Application
Properties:
Location: vpc.yml

# This stack contains the Amazon ECS cluster itself
ClusterStack:
Type: AWS::Serverless::Application
Properties:
Location: cluster.yml

# This stack contains the container deployment
ServiceStack:
Type: AWS::Serverless::Application
Properties:
Location: service.yml
Parameters:
VpcId: !GetAtt VpcStack.Outputs.VpcId
PublicSubnetIds: !GetAtt VpcStack.Outputs.PublicSubnetIds
ClusterName: !GetAtt ClusterStack.Outputs.ClusterName
ECSTaskExecutionRole: !GetAtt ClusterStack.Outputs.ECSTaskExecutionRole
ImageUrl: !Ref ImageUrl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File: parent.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Parent stack that deploys VPC, Amazon ECS cluster for AWS Fargate,
and a serverless Amazon ECS service deployment that hosts
the task containers on AWS Fargate
Parameters:
ImageUrl:
Type: String
Description: The url of the container image that you built

Resources:

# The networking configuration. This creates an isolated
# network specific to this particular environment
VpcStack:
Type: AWS::Serverless::Application
Properties:
Location: vpc.yml

# This stack contains the Amazon ECS cluster itself
ClusterStack:
Type: AWS::Serverless::Application
Properties:
Location: cluster.yml

# This stack contains the container deployment
ServiceStack:
Type: AWS::Serverless::Application
Properties:
Location: service.yml
Parameters:
VpcId: !GetAtt VpcStack.Outputs.VpcId
PublicSubnetIds: !GetAtt VpcStack.Outputs.PublicSubnetIds
ClusterName: !GetAtt ClusterStack.Outputs.ClusterName
ECSTaskExecutionRole: !GetAtt ClusterStack.Outputs.ECSTaskExecutionRole
ImageUrl: !Ref ImageUrl
You should now have the following four YAML files:
  • vpc.yml - Defines the core networking setup for the application
  • cluster.yml - Defines the Amazon ECS cluster that will launch AWS Fargate tasks
  • service.yml - Defines the settings for the Bun application, and the DynamoDB table it will connect to
  • parent.yml - Orchestrates launching the previous three stacks on your AWS account.
Deploy using the following command:
1
2
3
4
5
6
sam deploy \
--template-file parent.yml \
--stack-name bun-fargate-hitcounter \
--resolve-s3 \
--capabilities CAPABILITY_IAM \
--parameter-overrides ImageUrl=${REPO_URI}:bun-app

Test it out

Open up the Amazon ECS console and locate the deployed service. The cluster and service name will be autogenerated, but will look something like this:
  • Cluster: bun-fargate-hitcounter-ClusterStack-*
  • Service: web
Click into the service details and then select the "Network" tab. Under the section "DNS Names" you can see the public facing load balancer ingress URL. Click on "open address" or copy and paste the URL to your browser window.
You will see a number, which is the number of web requests that have hit this endpoint so far. You can reload the URL to see the number count up.

Tear it down

You can use the following commands to clean up when you are done:
1
2
3
4
5
# Delete the Amazon ECS deployment
sam delete --stack-name bun-fargate-hitcounter --no-prompts

# Empty and delete the Amazon ECR container registry we created
aws ecr delete-repository --repository-name sample-app-repo --force

Next steps

  • Try increasing the CPU and memory size of the Fargate task in the AWS::ECS::TaskDefinition in service.yml, or increasing the desired count of deployed tasks in the AWS::ECS::Service from two to ten.
  • Try running a load test with ab  or hey  to see how Bun in AWS Fargate performs under load.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article