Deploying a TypeScript Fastify API to AWS ECS Fargate using CDK
In this blog post, we will deploy a simple HTTP API using Fastify, written in TypeScript to AWS ECS Fargate using AWS CDK.
Prerequisites
- Node.js and npm installed on your system.
- Docker installed on your system.
What is AWS ECS Fargate?
Amazon Elastic Container Service (ECS) is a fully managed container orchestration service provided by AWS. ECS allows you to easily run and scale containerised applications on AWS, and it integrates seamlessly with other AWS services. Fargate is a deployment option for ECS that allows you to run containers without having to manage the underlying infrastructure.
Why use AWS CDK?
AWS Cloud Development Kit (CDK) is an open-source software development framework to define cloud infrastructure in code and provision it through AWS CloudFormation. With the CDK, you can define infrastructure as code using familiar programming languages like TypeScript, Python, or Java. The CDK offers several benefits, including:
- Easy to use: Developers can use familiar programming languages and modern development tools to define and deploy infrastructure, making it easier to manage infrastructure as code.
- Reusable: The CDK provides a library of pre-built AWS constructs, making it easy to reuse and share infrastructure code.
- Scalable: The CDK can be used to manage large-scale infrastructure deployments using the same familiar programming constructs used for smaller deployments.
- Secure: The CDK enforces best practices for security and compliance.
Getting Started
I won’t assume you’ve followed along with my previous blog posts, so let’s get our project up & running quickly:
Create a new Node.js project
First, create a new directory for your project and initialise a new Node.js project using npm. Run the following commands in your terminal:
npm install -g aws-cdk
mkdir fastify-docker
cd fastify-docker
cdk init app --language=typescript
Install Fastify
Next, install Fastify and save it as a dependency in your project using npm. Run the following command in your terminal:
npm install fastify @types/node --save
Create the “hello world” server
Now, create a new file called src/index.ts
in the root of your project directory. This file will contain the code for the “hello world” HTTP server.
import fastify from 'fastify'
const server = fastify()
const { ADDRESS = 'localhost', PORT = '3000' } = process.env;
server.get('/', async (request, reply) => {
return { message: 'Hello world!' }
})
server.listen({ host: ADDRESS, port: parseInt(PORT, 10) }, (err, address) => {
if (err) {
console.error(err)
process.exit(1)
}
console.log(`Server listening at ${address}`)
})
Add a build script
Let’s update package.json
to add a simple build script for our API:
{
"scripts": {
"build": "tsc -p tsconfig.json --outDir ./dist"
}
}
The --outDir
flag controls the directory where compiled code will be placed.
Create a new Dockerfile
First, create a new file called Dockerfile
in the root of your project directory. This file will contain the instructions for building your Docker image.
We’re going to re-use the multi-stage Dockerfile
I introduced in my previous blog post, but we’ll modify it to use the npm run build
script we added in the previous step.
# Stage 1: Install production dependencies
FROM node:16-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install --production
# Stage 2: Compile our application
FROM node:16-alpine as compiler
WORKDIR /app
COPY package*.json ./
RUN npm install && npm run build
# Stage 3: Create the production image
FROM gcr.io/distroless/nodejs:16
ENV ADDRESS=0.0.0.0 PORT=3000
WORKDIR /app
COPY package*.json ./
COPY --from=builder /app/node_modules .
COPY --from=compiler /app/dist .
CMD ["node", "dist/index.js"]
This Dockerfile
contains three stages:
- Stage 1: Install only production dependencies
- Stage 2: Compile our application from TypeScript
- Stage 3: Create the production image
In stage 1, we use the official Node.js 16-alpine image as our base image, set the working directory to /app
, copy the package*.json
files to the working directory, install dependencies using npm, copy the rest of the files to the working directory, and run the npm run build
command. This stage is responsible for building our application.
In stage 2, we are again using the official Node.js 16-alpine image as our base image, but this time we are installing all the necessary development & production dependencies in-order to run npm run build
. This stage is responsible for compiling our TypeScript code.
In stage 3, we use the distroless Node.js 16 image as our base image, set the working directory to /app
, copy the node_modules
and dist
folders from the previous stage to the working directory and set the default command to run the node dist/index.js
command. This stage is responsible for creating the production image.
Deploying a simple HTTP API to AWS ECS Fargate
We’ll be using the ApplicationLoadBalancedFargateService
construct that makes it easy to deploy our service. It takes care of creating and configuring several AWS resources, including:
- Amazon ECS cluster: A logical grouping of resources that are used to run containerized applications on Fargate.
- Amazon ECS task definition: A blueprint that describes how a container should be run, including information about the container image, CPU and memory requirements, and networking configuration.
- Amazon ECS service: A long-running task that runs on Fargate and is managed by ECS.
- Amazon Elastic Load Balancer (ELB): A load balancer that distributes traffic to the service.
- Amazon CloudWatch Logs group: A log group where logs generated by the service are stored.
Deploy to ECS Fargate using AWS CDK
We have now built our initial solution in TypeScript and have implemented a multi-stage Dockerfile
. Finally, need to update & deploy our stack to AWS using the CDK CLI. Before we do that, we need to make sure that we have configured our AWS credentials and set the default region in the AWS CLI.
The lib/cdk-stack.ts
file is where we will define the infrastructure resource for deploying the Fargate ECS CDK construct.
Let’s define the ApplicationLoadBalancedFargateService
construct. We will need to import the aws-ecs
and aws-ecs-patterns
module:
import * as cdk from 'aws-cdk-lib';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import { Construct } from 'constructs';
export class MyStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
memoryLimitMiB: 512,
cpu: 256,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('.'),
},
});
loadBalancedFargateService.targetGroup.configureHealthCheck({
path: '/',
});
}
}
In the updated MyStack
class, we have configured the ApplicationLoadBalancedFargateService
construct. We define where AWS CDK should look in-order to find the Dockerfile
we defined earlier in this post. AWS CDK takes care of building Docker Container and pushing it to a secure AWS ECR for us, during a deployment.
Finally, we configure a health check for the AWS Application Load Balancer, so that it knows the service is healthy and ready to receive traffic.
Deploying AWS CDK
To deploy AWS CDK, we first need to bootstrap our AWS environment. Bootstraping involves creating various resources to facilitate deployments and a new AWS CloudFormation stack that AWS CDK will use to store and manage its deployment artifacts.
Bootstrap AWS CDK
Once we have installed the AWS CLI, we can bootstrap AWS CDK by running the following command:
cdk bootstrap
Note: Running bootstrap more than once on a specific AWS Account & region has no effect.
Deploy Infrastructure Resources
After defining our infrastructure resources, we can deploy them using the AWS CDK CLI. To deploy our resources, run the following command:
cdk deploy
This command will build, package, and deploy our infrastructure resources to AWS.
Once the deployment is complete, you should see an output message that contains the URL of your HTTP API. You can use this URL to test your API by making a GET request to it.
Housekeeping
Once you’ve deployed everything, use the following command to destroy any deployed resources to avoid any unwanted cost:
cdk destroy
Conclusion
In this technical blog post, we walked through the steps of deploying a simple HTTP API to AWS ECS Fargate using the AWS CDKApplicationLoadBalancedFargateService
construct. We covered the basics of building a Fastify Docker container using TypeScript, AWS ECS Fargate and then deploying using CDK.
The ApplicationLoadBalancedFargateService
construct makes it easy to deploy containerised applications to AWS ECS Fargate. With the CDK, we can define and deploy infrastructure as code using familiar programming languages, making it easier to manage infrastructure at scale.