Deploying a NestJS Application to Amazon ECS using GitHub Actions and Terraform

JM Rifkhan - Sep 4 - - Dev Community

Introduction

In this blog, we will explore how to deploy a NestJS application to Amazon ECS (Elastic Container Service) using GitHub Actions for a seamless CI/CD pipeline. We’ll leverage several AWS services, including Application Load Balancer (ALB), Elastic Container Registry (ECR), ECS, ElastiCache for Redis, IAM, EC2, S3, and VPC, to create a microservice architecture.

Please Note: In the blog, I have only considered the deployment to ECS and the CICD security aspects. For managing environmental secrets securely, please ensure you use AWS Secrets Manager or SSM Parameter Store to manage and retrieve sensitive information, such as database credentials and API keys, within your ECS tasks and pipeline.

Prerequisites

Before getting started, ensure you have the following:

  • An AWS account
  • Basic knowledge of Docker, GitHub Actions, and AWS services
  • A pre-configured NestJS application with the structure detailed below
  • Terraform installed locally

GitHub URL of the NestJS application can be found here, and the Terraform scripts can be found here.

Containerizing the NestJS Application

The first step is to containerize the NestJS application using Docker. Below is a sample Dockerfile that you can use:

FROM node:lts-alpine3.19

# Create and set the working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json files
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code to the working directory
COPY . .

# Build the application
RUN npm run build

# Expose port and start application
EXPOSE 3000

CMD [ "node", "dist/main.js" ]

Enter fullscreen mode Exit fullscreen mode

This Dockerfile sets up the environment, installs dependencies, builds the NestJS application, and exposes port 3000, which is the default port for the application.

Create an ECS Cluster

Navigate to the Amazon ECS Console

  • Go to the Amazon ECS console.

Create a New Cluster:

  • Click on "Create Cluster".

Image description

Configure Cluster Settings:

  • Cluster Name:
  • Enter a name for your cluster. For example, nestjs-ecs-cluster.
  • The cluster name must be 1 to 255 characters long and can include a-z, A-Z, 0-9, hyphens (-), and underscores (_).

Image description

Infrastructure:

  • Select "Customized" to manually configure the cluster's infrastructure.

Image description

Configure Network Settings

VPC:

  • Select the VPC in which your ECS cluster will be deployed. If you don't have a specific VPC, you can use the default VPC or create a new one.

Subnets:

  • Select the subnets where your EC2 instances will be launched and where your tasks will run. It's recommended to use multiple subnets across different Availability Zones for redundancy.

Security Group:

  • Choose an Existing Security Group:
  • Select an existing security group that has the necessary inbound and outbound rules for your application.

Create a New Security Group:

  • Alternatively, create a new security group and configure the rules to allow traffic on the required ports, such as port 3000 for your NestJS application.

Auto-assign Public IP:

  • Set the Auto-assign public IP option to "Use subnet setting" or choose to auto-assign a public IP if your instances need to be accessible over the internet.

Image description

Review and Create the Cluster

Review Configuration:

  • Review all the settings you have configured for the cluster, EC2 instances, networking, and security.

Create Cluster:

  • Click "Create" to start the creation of your ECS cluster with the configured settings.
  • Wait for Cluster Creation

The creation process may take a few minutes. Once complete, you will have an ECS cluster set up with EC2 instances ready to run your tasks.

Image description

Register Task Definitions

  • Once your cluster is created, you can proceed to register task definitions that will run on the EC2 instances in this cluster.

Create an ECR Repository

Create a New Repository:

  • Click on "Create Repository".
  • Enter a name for your repository, such as nestjs-app-repo.
  • Leave the defaults for other settings unless you have specific requirements.
  • Click "Create Repository".

Image description

Push Your Docker Image to ECR:

  • After creating the repository, follow the steps provided by ECR to push your Docker image. This typically includes:
  • Authenticating Docker to the ECR registry.
  • Building your Docker image.
  • Tagging the image with your ECR repository URI.
  • Pushing the image to ECR.

Image description

Example commands:

Retrieve an authentication token and authenticate your Docker client to your registry. Use the AWS CLI:

aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws/d3k6a8t8
Enter fullscreen mode Exit fullscreen mode

Build your Docker image using the following command.

docker build -t nestjs-ecs-cluster/nestjs-app .
Enter fullscreen mode Exit fullscreen mode

After the build completes, tag your image so you can push the image to this repository:

docker tag nestjs-ecs-cluster/nestjs-app:latest public.ecr.aws/d3k6a8t8/nestjs-ecs-cluster/nestjs-app:latest
Enter fullscreen mode Exit fullscreen mode

Run the following command to push this image to your newly created AWS repository:

docker push public.ecr.aws/d3k6a8t8/nestjs-ecs-cluster/nestjs-app:latest
Enter fullscreen mode Exit fullscreen mode

Creating the Task Definition

After your Docker image is available in ECR, proceed to create the task definition in the ECS console:

Create a Task Definition:

  • Select "Task Definitions" from the side menu and click on "Create new Task Definition".

Image description

Choose Launch Type Compatibility:

  • Select the launch type (EC2 or Fargate) depending on your ECS cluster configuration.

Image description

Configure Container Definitions:

  • Add a container definition that points to the Docker image in your ECR repository.

Image description

  • Set up port mappings, environment variables, resource limits, and any other required settings.

Image description

For this application, we need the following environment variables:

PORT=3000
MONGODB_URI=mongodb://localhost:27017/test
REDIS_HOST=localhost
REDIS_PORT=6379"
Enter fullscreen mode Exit fullscreen mode

Save and Use the Task Definition:

  • Save the task definition and use it in your ECS service to deploy your NestJS application.

Creating an ECS Service

Create a New Service:

  • Click on "Create" under the Services tab in your cluster.

Compute Configuration:

Compute options:

  • Select "Capacity provider strategy" as the Compute options if you're using EC2 instances for the service.

Task Definition:

  • Select the task definition you previously created that references your Docker image in ECR.

Image description

Service Name:

  • Enter a name for your service, such as nestjs-service.
    Number of Tasks:

  • Specify the desired number of tasks to run simultaneously. For example, you can set this to 2.

Image description

Configure the Load Balancer (Optional but Recommended):

  • If you want to distribute traffic across your tasks, it's recommended to use an Application Load Balancer (ALB).

Load Balancing Type:

  • Select "Application Load Balancer".

Load Balancer Name:

  • Choose your existing ALB or create a new one if needed.
    Container to Load Balance:

  • Specify the container that will receive traffic from the load balancer. Choose the container port that you exposed in the task definition (e.g., port 3000 for your NestJS application).
    Target Group:

  • Create a new target group or select an existing one for your ECS tasks. This target group will be used to route traffic to the running tasks.

Health Check Path:

  • Specify the health check path that the ALB will use to monitor the health of your tasks. For example, if you have a /health endpoint in your NestJS app, set the health check path to /health.

Review and Create:

  • Review the configuration of your ECS service, including task definition, load balancing, and auto-scaling settings.
  • Click "Create Service" to deploy your service.

Image description

Transitioning to Automated Deployment

  • Up until now, we have been deploying the application using the AWS Management Console manually. Now, let's focus on automation. The Terraform script will create all the necessary AWS resources for this application, and we will use GitHub Actions to automate the deployment process.

Terraform directory structure:

.
├── backend.tf
├── locals.tf
├── main.tf
├── modules
│   ├── alb
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── ecr
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── ecs
│   │   ├── asg.tf
│   │   ├── ecs.sh
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── elasticache
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   ├── iam
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── mongodb_ec2
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── nsg
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   ├── s3
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   └── vpc
│       ├── main.tf
│       ├── outputs.tf
│       └── variables.tf
├── outputs.tf
├── providers.tf
├── terraform.tfvars
└── variables.tf
Enter fullscreen mode Exit fullscreen mode

Steps to Create AWS Resources Using Terraform

Clone the Terraform script template:

  • Clone the repository containing the Terraform script to your local machine.

Navigate to the Terraform template directory:

  • Move into the directory where the Terraform files are located.

Run the following command to initialize the working directory that contains the Terraform configuration files:

terraform init
terraform plan
terraform apply --auto-approve
Enter fullscreen mode Exit fullscreen mode

Image description

Consideration for the GitHub Actions Workflow

We need to ensure our ECS task definition is updated during the deployment process. I have created the following:

Please don't forget to update your AWS credentials under Settings > Secrets and Variables > Actions with the required AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.

ECS Task Definition File:

  • Located at .aws/tps-microservice-td.json This contains the configuration for our ECS task, including environment variables, container configuration, and resource limits.
{
  "containerDefinitions": [
    {
      "name": "tps-container",
      "image": "861569119044.dkr.ecr.us-west-2.amazonaws.com/nestify-ecr:33a0d65a43f0b7fb2daa9d15c6ca2a9af3fdbfe3",
      "cpu": 0,
      "portMappings": [
        {
          "name": "tps-container-3000-tcp",
          "containerPort": 3000,
          "hostPort": 3000,
          "protocol": "tcp",
          "appProtocol": "http"
        }
      ],
      "essential": true,
      "environment": [
        {
          "name": "REDIS_PORT",
          "value": "6379"
        },
        {
          "name": "PORT",
          "value": "3000"
        },
        {
          "name": "MONGODB_URI",
          "value": "mongodb://54.68.197.219:27017/test"
        },
        {
          "name": "REDIS_HOST",
          "value": "nestify-redis.qtsuer.0001.usw2.cache.amazonaws.com"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/tps-microservice-td",
          "awslogs-region": "us-west-2",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ],
  "family": "tps-microservice-td",
  "taskRoleArn": "arn:aws:iam::861569119044:role/ecsTaskExecutionRole",
  "executionRoleArn": "arn:aws:iam::861569119044:role/ecsTaskExecutionRole",
  "networkMode": "bridge",
  "cpu": "128",
  "memory": "128",
  "requiresCompatibilities": [
    "EC2"
  ],
  "runtimePlatform": {
    "cpuArchitecture": "X86_64",
    "operatingSystemFamily": "LINUX"
  }
}


Enter fullscreen mode Exit fullscreen mode

GitHub Actions Workflow:

  • The workflow file is located at .github/workflows/main.yml. This file handles the automation of our deployment process, including updating the ECS task definition when changes are made.
name: Deploy to Amazon ECS

on:
  push:
    branches:
      - master

env:
  AWS_REGION: us-west-2
  ECR_REPOSITORY: nestify-ecr
  ECS_SERVICE: tps-microservice-svc
  ECS_CLUSTER: nestify-ecs-cluster
  ECS_TASK_DEFINITION: .aws/tps-microservice-td.json
  CONTAINER_NAME: tps-container

jobs:
  build_and_deploy:
    name: Build and Deploy to ECS
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build Docker Image
        run: |
          docker build -t ${{ env.ECR_REPOSITORY }}:${{ github.sha }} .

      - name: Push Docker Image to Amazon ECR
        run: |
          docker tag ${{ env.ECR_REPOSITORY }}:${{ github.sha }} ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
          docker push ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}

      - name: Fill in the new image ID in the Amazon ECS task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: ${{ env.ECS_TASK_DEFINITION }}
          container-name: ${{ env.CONTAINER_NAME }}
          image: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}

      - name: Deploy Amazon ECS task definition
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true

Enter fullscreen mode Exit fullscreen mode

Image description

Once your pipeline completes, check with your ALB (Application Load Balancer) endpoint to verify the deployment.

Image description

That's it! You’ve successfully deployed your application on ECS using GitHub Actions and Terraform. 🚀😊

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player