🚀 Automating Deployment to Amazon ECS with GitHub Actions

OmkarOmkar
6 min read

As a DevOps engineer, I recently had the chance to work on a project that required setting up a fully automated CI/CD pipeline for a Node.js web application. This post will walk you through how I set up the GitHub Actions workflow to build, deploy, and test the application on Amazon ECS (Elastic Container Service).

Project Overview

The project is a Node.js application that needed to be containerized and deployed to AWS ECS. The deployment process required a seamless CI/CD pipeline to ensure that every code commit to the main branch would automatically trigger a deployment to the production environment.

GitHub Actions Workflow Breakdown

Here's the detailed breakdown of the deployment.yml file that powers this automation:

1. Defining the Workflow and Trigger

name: Deploy to Amazon ECS

on:
  push:
    branches:
      - main

This section defines the workflow name and specifies that the workflow will trigger on every push to the main branch. This ensures that any code changes in the main branch automatically initiate the deployment process.

2. Environment Variables

env:
  AWS_REGION: us-east-1
  ECR_REPOSITORY: devops-app
  ECS_SERVICE: devops-service
  ECS_CLUSTER: Devops-dev-cluster
  ECS_TASK_DEFINITION: ./ECS_TASK_DEFINITION.json
  CONTAINER_NAME: my-node-app

Here, I’ve defined several environment variables that will be used throughout the workflow, including the AWS region, ECR repository name, ECS service, cluster, task definition, and the container name.

3. Job Definition and Steps

3.1 Checkout Code

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout
        uses: actions/checkout@v4

The workflow runs on the latest Ubuntu version and the first step is to check out the code from the repository. This ensures that the latest codebase is available for the subsequent steps.

3.2 Configure AWS Credentials

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1

This step configures AWS credentials using secrets stored in GitHub. This is essential for authenticating and authorizing actions such as pushing Docker images to ECR or deploying to ECS.

3.3 Login to Amazon ECR

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

Logging into Amazon ECR allows the workflow to push the built Docker image to the specified ECR repository.

3.4 Build, Tag, and Push Docker Image

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

In this critical step, the Docker image is built, tagged with the commit SHA (ensuring each image is uniquely identifiable), and pushed to the ECR repository. The image URI is then stored for later use in the ECS task definition.

3.5 Update ECS Task Definition

      - name: Fill in the new image ID in the Amazon ECS task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@c804dfbdd57f713b6c079302a4c01db7017a36fc
        with:
          task-definition: ${{ env.ECS_TASK_DEFINITION }}
          container-name: ${{ env.CONTAINER_NAME }}
          image: ${{ steps.build-image.outputs.image }}

This step updates the ECS task definition with the new Docker image URI. This ensures that the ECS service will use the latest image when redeploying the task.

3.6 Deploy to ECS

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

In this step, the updated task definition is deployed to the specified ECS service. The workflow waits for the service to stabilize, ensuring the deployment is successful.

3.7 Integration Tests

     - name: Integration Tests
        id: integration-tests
        run: |
          curl -s http://devops-lb-1004528343.us-east-1.elb.amazonaws.com/home | grep "Vite + React"

Once the deployment is complete, this step runs integration tests to validate the deployment. For instance, it checks whether the home page contains the expected content, confirming that the application is running correctly.

3.8 Rollback on Failure

      - name: Rollback on Failure
        if: failure()
        uses: aws-actions/amazon-ecs-deploy-task-definition@df9643053eda01f169e64a0e60233aacca83799a
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true
          rollback: true

In case any step fails, this rollback mechanism reverts the deployment to the previous stable version, ensuring minimal downtime and service disruption.

3.9 Notify on Failure

    - name: Notify on Failure
        if: failure()
        run: |
          echo "Deployment failed. Rollback performed. Check logs for details."

Finally, if a rollback occurs due to a failure, this step provides a notification, making it easier to diagnose and troubleshoot the issue.

Leveraging AWS for Seamless Container Management

One of the core components of this project was setting up and utilizing AWS services to manage and deploy the containerized application. Here's how I structured the AWS environment:

1. Amazon ECR (Elastic Container Registry) Setup

To efficiently manage and store Docker images, I set up an Amazon ECR registry. This registry serves as a secure and scalable repository where Docker images are pushed after being built. By integrating ECR with GitHub Actions, the deployment pipeline automatically pushes new images to the registry whenever a commit is made to the main branch.

2. Creating an ECS Cluster

With the Docker images stored in ECR, the next step was to create an Amazon ECS (Elastic Container Service) cluster. ECS provides a highly scalable, high-performance container management service, making it ideal for deploying and running containerized applications. I configured the ECS cluster to ensure it could handle the load and provided the necessary resources for smooth application operation.

3. ECS Service Configuration and Task Definition

To manage the containers within the ECS cluster, I created an ECS service. This service is responsible for running and maintaining a specified number of task instances, ensuring that the application remains highly available. The service uses an ECS_TASK_DEFINITION.json file, which I configured to define how the Docker containers should be run. The task definition includes details like the Docker image URI, CPU, memory requirements, and networking settings.

4. Automated Deployment with GitHub Actions

The GitHub Actions workflow I set up is tightly integrated with the AWS environment. When a commit is made to the main branch:

  • The workflow checks out the latest code.

  • It builds a Docker image of the application and pushes it to the Amazon ECR repository.

  • The ECS task definition is updated with the new Docker image URI from ECR.

  • The updated task definition is deployed to the ECS service, ensuring the cluster runs the latest version of the application.

5. High Availability with ALB

To ensure the application is accessible and can handle traffic efficiently, I configured an Application Load Balancer (ALB) on top of the ECS service. The ALB distributes incoming traffic across the two replicas running in the ECS cluster, ensuring high availability and fault tolerance. This setup allows users to access the application seamlessly, even under varying loads.

Conclusion

This project was a fantastic experience in setting up a fully automated, reliable, and scalable deployment pipeline using GitHub Actions and AWS ECS. The combination of Docker, ECS, and GitHub Actions made it possible to automate the entire deployment process, ensuring that the application is always up-to-date with the latest code changes.

Key Takeaways:

  • Automation: GitHub Actions can automate complex deployment pipelines, reducing manual work and minimizing errors.

  • Scalability: Leveraging AWS ECS allows for scalable and reliable deployments of containerized applications.

  • Resilience: Implementing rollback functionality ensures that deployment failures do not impact the stability of the production environment.

You can check out the full code and explore more in the GitHub Repository. Stay tuned for more insights and deep dives into DevOps practices!

0
Subscribe to my newsletter

Read articles from Omkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Omkar
Omkar