Setting up a CI-CD workflow for a ReactJS app with GitHub Actions

Setting up a CI-CD workflow for a ReactJS app with GitHub Actions

Implement CI-CD to automatically test and deploy your code

CI and CD stand for continuous integration and continuous delivery/continuous deployment. In very simple terms, CI is a modern software development practice in which incremental code changes are made frequently and reliably

Prerequisites

To get the most out of this article, you should:

  • Have your React app on a GitHub Repo

  • Know How to use Git for version control

  • Have a Linux VM

  • Some knowledge on Github Actions

  • Some knowledge on Github Runners

Why is CI/CD important?

CI/CD allows organizations to ship software quickly and efficiently. CI/CD facilitates an effective process for getting products to market faster than ever before, continuously delivering code into production, and ensuring an ongoing flow of new features and bug fixes via the most efficient delivery method.

A real life Example(Vercel)

Vercel accelerates your frontend workflow with built-in CI/CD. You can use Vercel with GitHub as your CI/CD provider to generate Preview Deployments for every git push and deploy to Production when code is merged into the main branch. You just push your code on Github and vercel takes care of Testing your code and Deploying it to your Custom URL

What is Github Actions

An action is a custom application for the GitHub Actions platform that performs a complex but frequently repeated task. GitHub Actions allows you automate, customize, and execute software development workflows right in your GitHub repository.

What is a Workflow

A workflow is a configurable automated process that will run one or more jobs. Workflows will run when triggered by an event in your repository eg a push occurs. The workflow configuration is defined by a YAML file. Workflows are defined in the .github/workflows directory in a repository, and a repository can have multiple workflows, each of which can perform a different set of tasks. For example, you can have one workflow to build and test pull requests, another workflow to deploy your application every time a release is created

First lets create our Workflow File. Create a directory for your yaml file in .github/workflows/ then create an empty file main.yaml in the workflows folder

Committing the workflow file to a branch in your repository triggers the push event and runs your workflow. So in your main.yaml file enter this code

# A name for our Workflow
name: Build and Deploy React App

# Specifies that the workflow should be triggered on a Git push event.
on:
  push:
    branches:
      #Specifies the branches on which the workflow should be triggered
      - main
#Defines the jobs to be executed as part of the workflow
jobs:
  #The name of the job, in this case, "CI-CD."
  CI-CD:
    #Specifies that the job should run on the latest version of the Ubuntu operating system.
    runs-on: ubuntu-latest
    #Lists the individual steps to be executed within the job.
    steps:
      #Checks out the source code repository into the runner's workspace using the `actions/checkout@v2` GitHub Action.
      - name: Checkout Repository
        uses: actions/checkout@v2
      #Sets up Node.js on the runner using the `actions/setup-node@v3` GitHub Action. It specifies that Node.js version 16 should be used.
      - name: Set Up Node.js
        uses: actions/setup-node@v3
        # Allows passing additional parameters to the action, in this case, specifying the Node.js version.
        with:
          node-version: 16
      #Installs project dependencies using the `npm install` command.
      - name: Install Dependencies
        run: npm install
      #Builds the React app using the `npm run build` command.
      - name: Build React App
        run: npm run build
      #Adds an SSH private key to the SSH agent
      - name: Add SSH Key to Agent
        uses: webfactory/ssh-agent@v0.5.4
        with:
          ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY_2 }}
      #Performs the actual deployment.
      #It consists of a multi-line script (run:) Prints the current working directory and lists its contents.
      #Outputs a message indicating the start of the deployment.
      #Use SSH to connect to the CentOS 7 server (${{ secrets.SSH_HOST }}) with the specified port, user, and private key. It then changes to the deployment directory (/usr/share/nginx/portfolio/build), removes existing contents, and exits.
      #Use scp to securely copy the contents of the local ./build/* directory to the specified deployment directory on the server.
      #Outputs a message indicating the completion of the deployment.
      - name: Deploy React App to Linux CentOS 7
        run: |
          echo "Current Working Directory: $(pwd)"
          echo "Contents of the directory: $(ls -la)"
          echo "Starting deployment....."
          ssh -o StrictHostKeyChecking=no -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd /usr/share/nginx/portfolio/build && rm -rf * && exit"
          scp -o StrictHostKeyChecking=no -P ${{ secrets.SSH_PORT }} -r ./build/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/usr/share/nginx/portfolio/build
          echo "Deployment completed."

About Github Runners

Runners are the machines that execute jobs in a GitHub Actions workflow. For example, a runner can clone your repository locally, install testing software, and then run commands that evaluate your code. GitHub provides runners that you can use to run your jobs(github hosted runners), or you can host your own runners(self-hosted runners).

GitHub-hosted runners are GitHub hosted virtual machines to run workflows. The virtual machine contains an environment of tools, packages, and settings available for GitHub Actions to use. However feel free to use a self-hosted one if you want.

To use a GitHub-hosted runner, create a job and use runs-on to specify the type of runner that will process the job, such as ubuntu-latest check my workflow YAML file above

Continuous Deployment(CD)

So far on push event we can build and test the app(CI) but we need to Deploy the app on our Remote Linux Server. The approach is to have the job run some deploy scripts. But first we need to authorise the runner to execute the CD job. I need to generate ssh keys. GitHub Actions does not provide an interactive terminal for entering passphrases, so we need to use a passphrase-less key

ssh-keygen -t rsa -b 4096 -f ~/.ssh/deploy -N "" -C "https://github.com/username/repository"

Great now you have your private key deploy and public key deploy.pub Copy the contents of your public key to your servers authorised keys file in ~.ssh/authorised_keys and add it in a new line.

Now you need to add a few Action Secrets to enable your runner interact with the remote server. On your repo under Settings>Security>Secrets and Variables>Actions define a few variables like SSH_USER,SSH_HOST and SSH_PORT,SSH_PRIVATE_KEY

Great! now copy your private key deploy as a SSH_PRIVATE_KEY secret. Fill in the other values like your user, host and port. This values will enable the runner execute the script below that actually deploys the app on the remote linux server

      - name: Deploy React App to Linux CentOS 7
        run: |
          echo "Current Working Directory: $(pwd)"
          echo "Contents of the directory: $(ls -la)"
          echo "Starting deployment....."
          ssh -o StrictHostKeyChecking=no -p ${{ secrets.SSH_PORT }} ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "cd /usr/share/nginx/portfolio/build && rm -rf * && exit"
          scp -o StrictHostKeyChecking=no -P ${{ secrets.SSH_PORT }} -r ./build/* ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/usr/share/nginx/portfolio/build
          echo "Deployment completed."

The code above performs the actual deployment. It consists of a multi-line script (run:) that

  • Prints the current working directory and lists its contents.

  • Outputs a message indicating the start of the deployment.

  • Use SSH to connect to the CentOS 7 server (${{ secrets.SSH_HOST }}) with the specified port, user, and private key.

  • It then changes to the deployment directory /usr/share/nginx/portfolio/build, delete existing contents, and exits.

  • It then uses SCP to securely copy the contents of the runners local ./build/* directory to the specified deployment directory on the remote server.

  • It finally outputs a message indicating the completion of the deployment.

Triggering your workflow

Push to the main branch to trigger the workflow. The runner will run the jobs you specified, build the code, test it and finally run deployment scripts that ssh to your remote server, scp build files and extract it to your deployment folder. Navigate to your deployment URL on your browser and you'll see the changes have effected

Other tasks like deploying to a custom domains, setting up nginx, reverse proxies, creating users and setting up user permissions i will leave that to you. I have also written such articles in my blog check them out for help. Great!

Conculusion

Great! Phew. We've sucessfully setup CI-CD and automated a react app deployment with Github Actions. Now you've learned what is CI-CD is, what is a workflow, what are GitHub Actions and how you can automatically test the source code, generate a test coverage report, build and deploy the project on a linux server. All these jobs are activated by a push or pull request event on master branch