Objectives:
Containerise a Web Application: Dockerise a simple web application.
Set Up AWS ECS: Deploy the Dockerised application to AWS ECS (Elastic Container Service).
Implement CI/CD Pipeline: Use GitHub Actions to automate the deployment process.
Step-by-Step Guide
Prerequisites:
AWS Account, IAM user with programmatic access keys
Basic knowledge of Docker, AWS, and GitHub Actions
Installed tools: Docker, AWS CLI, Git, GitHub CLI
Step 1: Containerise the Web Application
Create a simple Node.js web application
Create a new directory for your project:
mkdir my-web-app cd my-web-app
Initialise a new Node.js project:
npm init -y
Install Express:
npm install express
Create an
index.js
file:const express = require('express'); const app = express(); const PORT = process.env.PORT || 3000; app.get('/', (req, res) => { res.send('Hello World!'); }); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });
Create a Dockerfile
FROM node:14 WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD ["node", "index.js"]
Build and test the Docker image locally
Build the Docker image:
docker build -t my-web-app .
Run the Docker container:
docker run -p 3000:3000 my-web-app
Visit
http://localhost:3000
to ensure the app is running.
Step 2: Push Docker Image to AWS ECR
Create an ECR repository
Go to the AWS Management Console, navigate to ECR, and create a new repository named
my-web-app
.Authenticate Docker to ECR
Use the AWS CLI to authenticate Docker to your ECR registry:
aws ecr get-login-password --region <your-region> | docker login --username AWS --password-stdin <your-aws-account-id>.dkr.ecr.<your-region>.amazonaws.com
Tag and push the Docker image
Tag the Docker image:
docker tag my-web-app:latest <your-aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/my-web-app:latest
Push the Docker image to ECR:
docker push <your-aws-account-id>.dkr.ecr.<your-region>.amazonaws.com/my-web-app:latest
Step 3: Set Up AWS ECS
Create a new ECS cluster
Go to the ECS console, create a new cluster with AWS Fargate (serverless) option leaving the rest as defaults.
Create a new task definition
Go to Task Definitions, create a new task definition:
Launch type: Fargate
Container: Add container
Select the Image pushed earlier to ECR:
Create a service
Go to Services, create a new service:
Launch type: Fargate
Cluster: Select your cluster
Task definition: Select your task definition
Number of tasks: 1
Wait until the service is created and a task is deployed, this could take few minutes. Once done test the app by selecting the task and finding the public IP address, then paste in a new browser tab followed by port number 3000
Finally, export the task definition file and paste it to the application working directory
Step 4: Implement CI/CD Pipeline with GitHub Actions
Create a
.github/workflows
directoryIn your project directory, create the following structure:
mkdir -p .github/workflows
Create a GitHub Actions workflow file
Create a new file
.github/workflows/aws.yml
with the following content:name: Deploy to Amazon ECS on: push: branches: [ "master" ] env: # set this to your preferred AWS region, e.g. us-west-1 AWS_REGION: us-east-1 # set this to your Amazon ECR repository name not the URI ECR_REPOSITORY: my-web-app # set this to your Amazon ECS service name ECS_SERVICE: my-web-app # set this to your Amazon ECS cluster name ECS_CLUSTER: my-web-app # set this to the path to your Amazon ECS task definition # file, e.g. .aws/task-definition.json ECS_TASK_DEFINITION: my-web-app.json # set this to the name of the container in the # containerDefinitions section of your task definition CONTAINER_NAME: my-web-app permissions: contents: read jobs: deploy: name: Deploy runs-on: ubuntu-latest environment: production steps: - name: Checkout uses: actions/checkout@v4 - 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: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - 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: | # Build a docker container and # push it to ECR so that it can # be deployed to ECS. 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 - 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.build-image.outputs.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
Set up GitHub
Create a new repo in Github to host the code, and set it up to allow Github Actions to deploy to ECS by configuring the secret keys for the IAM user:
Your-Repo>Settings>Environments>Select your env>Add environment secret
Step 5: Commit and Push Changes
Initialize a git repository and push to GitHub
git init git remote add origin <your-github-repo-url> git add . git commit -m "Initial commit" git push -u origin main
This will trigger the GitHub Actions workflow, which will build the Docker image, push it to ECR, and deploy it to ECS.
Congratulations! You have successfully set up a CI/CD pipeline to deploy a Dockerised web application to AWS ECS using GitHub Actions.
Test the workflow by committing a change to the index.js in the line:
res.send('Hello World!');
Then push the change to Github, this should trigger the workflow to deploy the new change to the docker image and create a task based on this new image.
Once the workflow is finished, browse to ECS on the management console and find the IP address of the newly launched task to test it on the browser.