🚀 Automating Deployment to Amazon ECS with GitHub Actions
Table of contents
- Project Overview
- GitHub Actions Workflow Breakdown
- 1. Defining the Workflow and Trigger
- 2. Environment Variables
- 3. Job Definition and Steps
- 3.1 Checkout Code
- 3.2 Configure AWS Credentials
- 3.3 Login to Amazon ECR
- 3.4 Build, Tag, and Push Docker Image
- 3.5 Update ECS Task Definition
- 3.6 Deploy to ECS
- 3.7 Integration Tests
- 3.8 Rollback on Failure
- 3.9 Notify on Failure
- 1. Amazon ECR (Elastic Container Registry) Setup
- 2. Creating an ECS Cluster
- 3. ECS Service Configuration and Task Definition
- 4. Automated Deployment with GitHub Actions
- 5. High Availability with ALB
- Conclusion
- Key Takeaways:
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!
Subscribe to my newsletter
Read articles from Omkar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by