Stafini
BlogFlashcardsProjectsResume

Deploy Web Stack to AWS EC2 - Part 2

Previously in part 1, we have already done with manual deployment our web stack to AWS EC2, yeah I mean manual, by that means we have to log into EC2 then pull the code, then restart pm2 everytime we want to deploy.

There are many tools and platforms that can be used to implement automated deployment, such as Jenkins, Travis CI, GitHub Actions, and AWS CodeDeploy. These tools typically integrate with version control systems like Git and can be configured to trigger deployment workflows based on events such as code commits, pull requests, or scheduled builds. We will pick GitHub Actions, it would be good foundation to learn about GitHub Actions for upcoming post. To learn more about actions, we can try multiple approaches:

GitHub Actions Script Execution on Remote Server

TL;DR:

  • Create an .sh file in AWS EC2 project folders.
  • Configure GitHub Actions to ssh to EC2 and run .sh script file

Create .sh file in EC2 instace

Navigate to project folder and create .sh file for Backend

ubuntu@ip-172-31-18-43:~/apps$ cd apps/ec2-app
ubuntu@ip-172-31-18-43:~/apps/ec2-app$ sudo nano deploy-be.sh

Let's try to put something to do a test run:

#!/bin/bash

echo "Hello from the deploy script!"

Save and close the file Control + O => Control + X, add executable permission:

ubuntu@ip-172-31-18-43:~/apps/ec2-app$ chmod +x deploy-be.sh 

Run the script:

ubuntu@ip-172-31-18-43:~/apps/ec2-app$ ./deploy-be.sh
Hello from the deploy script!

Ok, we're doing well.

Configuring Github Actions

First we must set secrets for action, navigate to repository setting page, select Secrets and Variables >> Actions

How do we get these data ? Copy SSH connect command from EC2 instance, example:

ssh -i "ec2-exp.pem" [email protected]
  • SERVER_SSH_KEY: open .pem pair-key file when you first create EC2 instance and copy its content
  • SERVER_USERNAME: left part of @ in command, should be ubuntu.
  • SERVER_HOST: right part of @ in command, should be ec2-18-141-207-114.ap-southeast-1.compute.amazonaws.com.

With that, we're set to go, create a yaml file in .github/workflows folder.

name: Deploy AWS EC2 

on: 
  push:
    branches:
      - main

jobs:
  deploy:
    name: Deploy to EC2 with executable SH
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Checkout code and SSH into instance and run script
        env:
          PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
          USER_NAME: ${{ secrets.SERVER_USERNAME }}
          HOSTNAME: ${{ secrets.SERVER_HOST }}
        run: |
          echo "$PRIVATE_KEY" > private_key && chmod 600 private_key
          ssh -o StrictHostKeyChecking=no -i private_key ${USER_NAME}@${HOSTNAME} '

              # Now we have got the access of EC2 and we will start the deploy .
              cd /home/ubuntu/apps/ec2-app/
              ./deploy-be.sh
              '

Don't forget to push yaml to repository. Now the actions we just created will run everytime we push to ec2-sh-executable. Noted that this is only for Backend, for Frontend we also need to create the same yaml and set secrets

Our action is on at Actions page, let's make some changes to see how it runs

OK great, now we can run sh script from inside actions, we just need to update bash script to run redeployment process.

Update .sh script

#!/bin/bash

echo "starting deployment process"

# pull latest source code
echo "get latest srouce code"
cd be
git pull origin main

# do a clean installation of all depepdencies
echo "instaling dependencies"
npm ci

# restart application

echo "restart application"
pm2 restart be

echo "deployment process completed"

Nothing fancy here, we just run some command lines for:

  • pulling latest source code
  • do a clean dependency installation
  • restart pm2 process for application

We will perform change in repository one last time to see how actions run.

Let's also create one for Frontend:

nano touch deploy-fe.sh
cat deploy-be.sh >> deploy-fe.sh # copy and paste content since they're similiar

Update command accordingly for Frontend

#!/bin/bash

echo "starting deployment process"

# pull latest source code
echo "get latest srouce code"
cd fe
git pull origin main

# do a clean installation of all depepdencies
echo "instaling dependencies"
npm ci

# create build
echo "building frontend"
npm run build

# restart application
echo "restart application"
pm2 restart fe

echo "deployment process completed"

GitHub Action Runner Integration with EC2

This approach feels more methodical compared to previous one as you don't manually write command lines in actions to run .sh file, I feel like we should explode possible ways to do this, might come in handy later.

Set up Github Action Runners

Git Action Runner acts as a link between our GitHub repository and the EC2 instance. This integration allows direct interaction between the two and enables automated build, test, and deployment processes.

To download and configure a Git Action Runner on our EC2 instance:

  • Go to the GitHub repository and click on Settings.
  • On the left-hand sidebar, click on Actions then select Runners.
  • In the Runners page click on the New self-hosted runner button.
800

Note: While running the command, it may prompt some setup questions, we can simply press Enter to skip to the default options.

After running the ./run.sh command, If the agent returns a ✅ Connected to GitHub message, it indicates a successful installation.

Next, we’ll install a service to run our runner agent in the background:

sudo ./svc.sh install
sudo ./svc.sh start

The above code will start our runner service in the background, making it ready to execute workflows whenever triggered.

Create yaml file

Create .yaml file in .github/workflows content with the commands below:

name: AWS EC2 CI/CD

on:
  push:
    branches: 
     - main

jobs:
  build:
    runs-on: self-hosted
    strategy:
      matrix:
        node-version: [18.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm ci
    - run: pm2 restart fe

As we can see, when workflow runs, git will connect directly to our project folder at EC2 then we run command with run one after others.


Resource: