Deployment Automation using GitHub Actions with ECS

Wynn
6 min readJun 20, 2024

--

Deployment Automation (Django + Docker + ECS + Fargate)

AWS ECS

Cloud service has become an indispensable part for us, like air.
I still vividly remember the exhilarating feeling when I first deployed my clumsy web application to an EC2 instance. However, as a novice developer back then, I encountered numerous challenges, particularly with:

  • Nginx
  • Security Group
  • SSH Key
  • Running the server in the background

These components didn’t provide clues suitable to my beginner level, leading to significant trial and error.

Manual Deployment Challenges

While initial deployment costs are justifiable, connecting to the instance via SSH key and running git pull for every update is tedious. Although SSH key access is straightforward, novice developers often feel an uneasy reluctance towards directly accessing instances. The reluctance leads to infrequent updates and can diminish interest in the service being developed.

Introduction to Automation with GitHub Actions

To address these issues, we’ll automate the process of dockerizing our application and deploying it to AWS using GitHub Actions. this may sound daunting, but by leveraging AWS services, we’ll just need to provide the correct configuration values. Deployment becomes repetitive and mundane once the process is established, and as developers, avoiding repetitive tasks is our destiny.

Prerequisites

  • Create a Django proejct
  • Install Docker

Building the Docker Image

Before writing the Dockerfile, list the required Python libs in requirements.txt . for this example, we’ll use django and mysqlclient .

django==3.1
mysqlclient==2.0.1

The `Dockerfile` executes a set of commands (DSL) to create the Docker image. Let’s create a Dockerfile in the project’s root directory.

FROM python:3.7
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt /app/
RUN pip install -r requirements.txt
COPY . /app/
EXPOSE 8080
CMD ["python3", "manage.py", "runserver", "0.0.0.0:8080"]

Now build the Docker image using the following command:

docker build . -t ecs-tutorial

If logs like the below have been printed, it means the image build was successful.

> docker build . -t ecs-tutorial
Step 1/8 : FROM python:3.7
---> 2699987679cd
Step 2/8 : ENV PYTHONUNBUFFERED=1
---> Using cache
---> b1ecf661981c
Step 3/8 : WORKDIR /app
---> Using cache
---> 4f89f55da03a
Step 4/8 : COPY requirements.txt /app/
---> Using cache
---> 363862418ed6
Step 5/8 : RUN pip install -r requirements.txt
---> Using cache
---> ee9a55e82551
Step 6/8 : COPY . /app/
---> Using cache
---> c0b955af8faf
Step 7/8 : EXPOSE 8080
---> Using cache
---> ebb82e5e1931
Step 8/8 : CMD [ "python3", "manage.py", "runserver", "0.0.0.0:8080" ]
---> Using cache
---> ba3d6c22cbb9
Successfully built ba3d6c22cbb9
Successfully tagged ecs-tutorial:latest

You can see the list of images you have created by entering the docker images command.

REPOSITORY      TAG     IMAGE ID            CREATED           SIZE
ecs-tutorial latest 8b9365a9f930 2 seconds ago 919MB
...

Running the Docker Container

to run the image:

docker run -d -p 8080:8080 ecs-tutorial

Access 127.0.0.1:8080 to see the Django rocket welcoming you.

Creating a Repository in AWS ECR

ECR is a managed container image registry service by AWS. It securely stores Docker images, and integrates seemless with other AWS services.

Create a repository named repo-for-ecs-tutorial in ECR. We will later upload our Docker image here Using GitHub Actions.

Creating a Task Definition

A task definition in ECS is similar to docker-compose . It specifies the Docker image, port mappings, instance type, CPU, and memory allocations. Instead of using the AWS console, we’ll create a task-definition.json file.

Create the task-definition.json in your project root:

{
"executionRoleArn": "arn:aws:iam::your_account_id:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "ecs-tutorial",
"image": "your_account_id.dkr.ecr.region.amazonaws.com/repo-for-ecs-tutorial:latest",
"memory": 512,
"cpu": 256,
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"hostPort": 8080
}
]
}
],
"family": "ecs-tutorial"
}

Register the task definition using the AWS CLI:

aws ecs register-task-definition --cli-input-json file://task-definition.json

This command registers the task definition in your AWS account. You can verify the task definition in the AWS Management Console by navigating to the ECS service and selecting the “Task Definitions” tab.

Understanding Roles and Permissions

If you do not have an executionRoleArn created, follow these steps to create one:

  1. Open the IAM console at https://console.aws.amazon.com/iam/.
  2. In the navigation pane, choose “Roles,” then “Create role.”
  3. Select “Elastic Container Service” as the type of trusted entity.
  4. For the use case, choose “Elastic Container Service Task” and then select “Next: Permissions.”
  5. Search for AmazonECSTaskExecutionRolePolicy, select it, and proceed to the next step.
  6. Name the role ecsTaskExecutionRole and create the role.

Once created, the ARN for this role can be found in the IAM console under the role’s details. Use this ARN in your task-definition.json file.

By following these steps and using the provided example, you should have a comprehensive understanding of creating and registering a task definition for AWS ECS.

Creating an ECS Cluster and Service

Define an ECS cluster, which is a logical grouping of tasks of services. AWS provides a default clsuter, but we’ll create a new one named cluster-for-ecs-tutorial using the Network only template for Fargate.

Creating an Application Load Balancer

In the EC2 console, create an ALB named alb-for-ecs-tutorial . Configure the security settings to open port 80 to anywhere. Create a target group with IP as the target type. Associate this ALB with service created in ECS.

Automating Deployment With GitHub Actions

Configure GitHub Actions to deploy the Docker image to ECR and update the ECS service. Add the required secrets (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`) to GitHub. you can see the example of mine here.

Create a workflow file in your repository at .github/workflows/aws.yml :

name: Deploy to Amazon ECS

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2

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

- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: repo-for-ecs-tutorial
IMAGE_TAG: latest
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

- name: Deploy to Amazon ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: task-definition.json
service: ecs-tutorial-service
cluster: cluster-for-ecs-tutorial
wait-for-service-stability: true

Conclusion

By automating the deployment process, we can now focus more on developing our service. In future, we can explore advanced topics like applying SSL cerificates using AWS Certificate Manager, configuring HTTPS for ALB, and using AWS System Manager for environment variables.

References

this document has been created to help consolidate knowleged and make the deployment process smoother and more understandable.

--

--