Skip to main content

Leonardo Murça

How to Deploy React Applications Using Github Actions + Rsync

Posted on Oct 15, 2022

Source code and deployed app used for this post:

TL;DR

  • Create a user in your server to deploy your application:
$ useradd -s /bin/bash -d /home/tutorials -m tutorials
$ su tutorials
  • Create a folder to copy your production files to:
$ mkdir rsync-deploy-react-app
  • Add your private tutorials’s user ssh key to your Action Secrets.

  • Create a new github action to deploy your application and paste the code below:

# .github/workflows/deploy.yml
name: Deploy 

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-deploy:

    runs-on: ubuntu-latest

    env:
      SSH_KEY: ${{secrets.SSH_KEY}}

    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js 16
      uses: actions/setup-node@v3
      with:
        node-version: 16
        cache: 'npm'
    - run: mkdir ~/.ssh
    - run: 'echo "$SSH_KEY" >> ~/.ssh/id_rsa_tutorials'
    - run: chmod 400 ~/.ssh/id_rsa_tutorials
    - run: echo -e "Host tutorials\n\tUser tutorials\n\tHostname 45.76.5.44\n\tIdentityFile ~/.ssh/id_rsa_tutorials\n\tStrictHostKeyChecking No" >> ~/.ssh/config
    - run: npm install
    - run: npm run build
    - run: rsync -avz --progress build/ tutorials:/home/tutorials/rsync-deploy-react-app --delete

Pay attention to the Pre-requisites. That’s it! Change some code, push it to the main branch and see the magic happening!

Motivation

I’ve been working on my bachelor’s thesis in information systems, which is a simple React application, and I was struggling to find a simple, secure and fast way to deploy it to my own VPS. However, most of the content that I had found on the internet involves fancy and complex solutions like docker and Kubernetes or vendor locked solutions like Heroku and Vercel.

I recognize these tools have their advantages, but have found that for small to medium sized projects they are more effort than they are worth to maintain. All I need to do is build the code and copy the built files to the server. Then, rsync came to my knowledge.

Pre-requisites

  • An existing React Application;
  • An server or a hosting service to deploy your application;
  • Your own domain registered;
  • NGINX installed in your server;
  • Some knowledge about how to register a domain and create a server on some hosting platform (I’ll add articles about that in the future).

Demo App to Deploy

I’ve created an demo app to deploy it to my server. Its source code is available at rsync-deploy-react-app.

React Application Screenshot

Fig. 1 - React Application Screenshot.

Server Setup

For this tutorial, I’ll use my domain leomurca.xyz setting up a sub-domain for it. To be more specific, I’ll point tutorial.leomurca.xyz to my VPS’s IP: 45.76.5.44.

SSH to your server

$ ssh root@45.76.5.44

Create a user to manage your application

To prevent our pipeline to have root access to your server, I’ll create a user to manage deployments called tutorials:

$ useradd -s /bin/bash -d /home/tutorials -m tutorials

After that, change the user to it:

$ su tutorials

As I created the user named tutorials, this user will host for multiple tutorials, so in order to isolate our application, create a specific folder to house our build files:

$ mkdir rsync-deploy-react-app

Github Action Setup

Now let’s create the .github/workflows/deploy.yml to define the pipeline steps. First, add the label for the workflow and when it should be triggered:

name: Deploy

on:
  push:
    branches: [ "main" ]

Above, the workflow will be trigered every time that new code is pushed or merged to the main branch (This happens for Pull Requests merged to the main branch too).

Then, describe a new job that we will name it as build-and-deploy to handle all the steps to build and deploy our app:

jobs:
  build-and-deploy:

    runs-on: ubuntu-latest

    env:
      SSH_KEY: ${{secrets.SSH_KEY}}
    
    ...

The SSH_KEY: ${{secrets.SSH_KEY}} references Github Secret that is allowed to login in our server. It’s important to mention that we should add the secret to our repository settings.

Also, to avoid issues when authenticating to your server using ssh, use RSA generated keys to authenticate instead of Ed25519 keys. For more details on that, check this doc on how to generate a new SSH key.

Afterwards, we’ll start define the actual steps to be executed:

    ...
    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js 16
      uses: actions/setup-node@v3
      with:
        node-version: 16
        cache: 'npm'
    ...

These first steps will basically define which NodeJS version will be used in our pipeline.

And now, a very important step is to add the commands that will actually be executed in our workflow, pay attention to them:

    ...
    - run: mkdir ~/.ssh
    - run: 'echo "$SSH_KEY" >> ~/.ssh/id_rsa_tutorials'
    - run: chmod 400 ~/.ssh/id_rsa_tutorials
    - run: echo -e "Host tutorials\n\tUser tutorials\n\tHostname 45.76.5.44\n\tIdentityFile ~/.ssh/id_rsa_tutorials\n\tStrictHostKeyChecking No" >> ~/.ssh/config
    - run: npm install
    - run: npm run build
    ...

The configs above we basically:

  • Copied the SSH_KEY to a file;
  • Created an ssh config to our server using the key created before;
  • Downloaded the app dependencies and generate the build files to be copied to our server.

To make the ssh configs more readable, check the code snippet below:

Host tutorials
  User tutorials
  Hostname 45.76.5.44
  IdentityFile  ~/.ssh/id_rsa_tutorials
  StrictHostKeyChecking No

Using rsync to deploy to the server

And finally, add the rsync command to sync the files from the build/ folder to our server:

...
- run: rsync -avz --progress build/ tutorials:/home/tutorials/rsync-deploy-react-app --delete
...

The meaning of each flag used are:

  • -a: It is a quick way of saying you want recursion and want to preserve almost everything (with -H being a notable omission);
  • -v (-verbose): This option increases the amount of information the daemon logs during its startup phase.
  • -z (-compress): compresses the file data as it is sent to the destination machine, which reduces the amount of data being transmitted – something that is useful over a slow connection.
  • --progress: This option tells rsync to print information showing the progress of the transfer. This gives a bored user something to watch.
  • --delete: This tells rsync to delete extraneous files from the receiving side (ones that aren’t on the sending side), but only for the directories that are being synchronized.

To have more details on all the options for rsync, check its man page.

Complete .github/workflows/deploy.yml

name: Deploy 

on:
  push:
    branches: [ "main" ]

jobs:
  build-and-deploy:

    runs-on: ubuntu-latest

    env:
      SSH_KEY: ${{secrets.SSH_KEY}}

    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js 16
      uses: actions/setup-node@v3
      with:
        node-version: 16
        cache: 'npm'
    - run: mkdir ~/.ssh
    - run: 'echo "$SSH_KEY" >> ~/.ssh/id_rsa_tutorials'
    - run: chmod 400 ~/.ssh/id_rsa_tutorials
    - run: echo -e "Host tutorials\n\tUser tutorials\n\tHostname 45.76.5.44\n\tIdentityFile ~/.ssh/id_rsa_tutorials\n\tStrictHostKeyChecking No" >> ~/.ssh/config
    - run: npm install
    - run: npm run build
    - run: rsync -avz --progress build/ tutorials:/home/tutorials/rsync-deploy-react-app --delete

That’s it! Change some code, push it to the main branch and see the magic happening!

Github action screenshot

Fig. 2 - Github action screenshot.

Also, if you want to have more details on the action steps, please check the actions-executed during this article.

Conclusion

Simple, fast and secure, that are the main benefits of using the workflow mentioned in this tutorial. It’s really a relief to have these kind of tools in the middle of many bloated solutions.

If you have any questions or topics to talk about, please reach me out!