Deploy a Docker Container to Amazon ECS using GitHub Actions

Deploy a Docker Container to Amazon ECS using GitHub Actions

In this project, we will set up a GitHub Actions CI/CD pipeline that runs each time new changes are pushed to the main GitHub branch. When this action is triggered, a new docker image is built, pushed to Docker Hub, and then deployed to an Amazon ECS cluster.

This guide assumes you already have some knowledge in AWS, ECS, Docker, and GitHub Actions as the goal is to provide a template you can modify to meet the specific needs of your project.

Requirements

To follow along with this project, you should have an AWS Account, GitHub account, Docker Hub account(you can optionally use Amazon ECR), and certain software installed in your local environment like:

  • Git

  • Docker

  • VS Code(or any text editor you prefer)

Lastly, we will need an ECS cluster/ ECS Service up and running that GitHub actions will deploy the updated image to. I already wrote out this infrastructure using Terraform and you can get it here from my GitHub repo => https://github.com/ajimbong/terraform-ecs-cicd-project. If you are familiar with Terraform, you can use that to automatically create the infrastructure or do it manually. But these are the resources that the Terraform code will create:

AWS Fargate ECS Architectur Diagram

  • A VPC with 2 public and private subnets distributed across 2 availability zones.

  • An Internet Gateway and Two NAT Gateways, one in each availability zone.

  • A Fargate ECS Cluster and ECS Service.

  • An Application Load Balancer.

  • And a Route53 record which you can optionally leave out.

Implementation

Clone the Application Code

The application used here is a Simple Notes Taking app that was developed using React, it's purely a frontend application and doesn't have a backend. Here is the link to clone the code from my repo => https://github.com/ajimbong/novo-notes. Optionally, you could still skip this step by using your project.

Modify the Workflow file.

In this GitHub repo => https://github.com/ajimbong/novo-notes, navigate to the directory .github/workflows/ and you will find a main.yml file. This file is a declaration of the tasks GitHub Actions will perform when a "defined event" is triggered.

name: Deploy to Amazon ECS
on:
  push:
    branches:
      - "main"
  workflow_dispatch:

# secrets:
#   DOCKER_HUB_USERNAME: 
#   DOCKER_HUB_PASSWORD: 
#   AWS_ACCESS_KEY_ID:
#   AWS_SECRET_ACCESS_KEY:

env:
  IMAGE_NAME: novo-notes
  AWS_REGION: us-east-1                          
  ECS_SERVICE: ecs-cicd-ecs-service              
  ECS_CLUSTER: ecs-cicd-ecs-cluster              
  ECS_TASK_DEFINITION: aws/td.json 
  CONTAINER_IMAGE: ajimbong/novo-notes
  CONTAINER_NAME: novo-notes

defaults:
  run:
    shell: bash
jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    permissions:
      packages: write
      contents: read
    steps:
        - name: Checkout
          uses: actions/checkout@v2
        - name: Login to Docker Hub
          uses: docker/login-action@v2
          with:
            username: ${{ secrets.DOCKER_HUB_USERNAME }}
            password: ${{ secrets.DOCKER_HUB_PASSWORD }}
        - name: Set up Docker Buildx
          uses: docker/setup-buildx-action@v2
        - name: Build and push
          uses: docker/build-push-action@v4
          with:
            context: .
            file: ./Dockerfile
            push: true
            tags: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.IMAGE_NAME }}:latest
        - name: Configure AWS credentials
          uses: aws-actions/configure-aws-credentials@v1
          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: 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: ${{ env.CONTAINER_IMAGE }}
        - 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

Starting from the top and moving down:

  • The on: keyword species the events that should trigger the workflow. The events defined here are:

    • push: that runs anytime we push changes to the main branch

    • workflow_dispatch: that allows us to manually run the workflow.

  • The secrets that have been commented out refer to the secrets we manually have to create in our GitHub repository. Ensure that you set them up before proceeding.

  • env: is the set of environment variables that will be used during the execution of the workflow. You want to make sure you set the values that are appropriate to your project.

    • IMAGE_NAME: is the name of the docker container image.

    • AWS_REGION: refers to the AWS region where your resources are deployed to.

    • ECS_SERVICE: is the name of your ECS Service

    • ECS_CLUSTER: is the name of your ECS cluster

    • ECS_TASK_DEFINITION: is the directory to a JSON file containing your task definition template. This JSON code can easily be gotten from your AWS ECS console under Task Definitions, and it should be a Task Definition that was already created.

    • CONTAINER_IMAGE: is the URL to your docker container image.

    • CONTAINER_NAME: is the name of your docker image. Same as IMAGE_NAME.

  • defaults: we're just defining the default Linux shell that should be used.

  • jobs: this is where we define all the execution steps. Again, I already assume you have some familiarity with GitHub actions so I'm not going to explain all the steps as this will be very lengthy.

Run the Workflow

Now that we're done modifying the workflow file, it's time to run it by pushing the changes to the main GitHub branch to trigger the workflow, or we can manually run it from the Actions tab in the GitHub repo.

Watch closely to ensure that everything goes properly(which I hope it does), else you can try to troubleshoot any failures that occur.

This is where I will wrap things up, and I hope you found this article helpful!