Deploy React App to EC2 using GitHub Actions—a love story
Tags: react, aws, github, githubactions
Published: 2024-02-14 19:38:21

As my fingers danced on they keyboard the other week, I felt each line of code was destined to be hosted on an EC2 instance.
I couldn't help but wonder, why companies love AWS so much? This is a tale of a modern romance between code and cloud. Think of it as a step by step guide to deploy a Vite React app using the unsung hero of our love story: GitHub Actions.



**1. Create a Vite app and the GitHub repository**
```
npm create vite@latest <project name>
```
It will ask you a few questions about your project. Select React -> JavaScript. 

We can start to see the difference between create-react-app and Vite already. Remember to run `npm i` to install all dependencies and `git init` to initialise your local repo. Hook this up with your remote repository, but remember to commit first! 💍
And just like that, we say goodbye to create-react-app, our former flame.




**2. Create an EC2 Instance on AWS**
Next up is more fun! Let's create an EC2 instance, which is short for Elastic Compute; it is very flexible for our needs. AWS has a bunch of services that make this deployment easier like Elastic Beanstalk, but we are going to choose the rocky road because let's face it—it's just so much cooler. 



1. Log in to your AWS console and click on the search bar and type in EC2 to search for this service
2. Look for a big orange button called Launch Instance

_Leave everything on the default settings except the following:_
3. Give your instance a name; this could be your app's name for example
4. Select Ubuntu as OS
5. Remember to double check the prices, AWS gives you free months though which is helpful in this economy
6. Create a new keypair using its default settings and save it to your desktop or somewhere safe and accessible
7. Tick both allow HTTP/HTTPS tick boxes like on the image below:
![AWS security settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/brv18l9ejqka3eul2n3x.png)
8. Launch instance!



**3.SSH into our virtual machine**
This is the hacky part of the love story. We are finally logging into our virtual machine on the cloud. 

Go back the EC2 Instance Dashboard. 
1. Click on the instance ID
2. Click connect and then select SSH client
3. Follow the steps in your terminal: you're looking for 2 commands: ` chmod 400 "yourkey.pem"` and copy the example command. Remember your terminal should be in the directory where you kept your key file! Never share this file with anyone!
4. Let's run `sudo apt-get update` to install the latest updates
5. Install nginx by running `sudo apt-get install nginx`

Pro tip: Don't `cd` anywhere at this point! Your terminal will be in `/home/ubuntu/`. The folder called ubuntu is the user's folder which doesn't require admin rights like `sudo` when running commands to install GitHub Actions.

Nginx is a web server software that will serve the production version of our app.

**4. Set up GitHub Actions**
GitHub Actions is a godsend. We will set up a self-hosted runner which will run workflows and jobs on our EC2 instance.
 

1. Go to your repository settings
2. Select runners 
![GitHub Actions runners](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oc4k4dwzf39luxgy018m.png)
3. Click on 'new self hosted runner'
4. Remember to select Linux because our EC2 instance runs on Linux!

The next steps will install GitHub actions runner on our virtual machine:

- Copy and paste commands one by one from GitHub into our EC2 instance **(except the last one `./run.sh`!)** If you run the last command, just press Control + C to stop the process.

_Keep pressing enter to set the default settings when it prompts you._

We should see something like this:

![GitHub Actions Terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f2f342rwi75c1pp4dbmg.png)

Instead of the last command `./run.sh`, we will install the runner as a [service](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/configuring-the-self-hosted-runner-application-as-a-service) to listen for jobs in the background:
```
sudo ./svc.sh install
sudo ./svc.sh start
```

Once this is done, you should refresh the GitHub page after a few minutes and check the self hosted runner is idle. Sometimes, it helps if you click on runners, and actions a few times before this comes up! 

![Self Hosted Runner Idle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j14mz0wlb6udhz98hdu3.png)

**5. Set up the workflow in the repository**
In this step, we are going to set up the workflow; a bit like teaching EC2 how we operate.

1. Click on Actions (next to the Pull requests button) and add a new workflow
2. Let's search for a Node workflow and select configure

This will create a new folder and a file in your project `.github/workflows/node.js.yml`!

In this .yml file:

1. I removed the pull request job
2. I updated **runs-on: self-hosted** This means that the job will run on our EC2 instance as we set up earlier instead on a throw away like instance that GitHub provides.
3. I updated the node version to `node-version: [20.x]`
4. I removed the test command at the very end
5. I added 2 **important** jobs:
   
- Clean up the /var/www/html/ folder. Nginx creates these folders and uses them serve our static files. We will remove the default files.
 ```
- name: Clear contents of /var/www/html
  run: sudo rm -rf /var/www/html/*
```

- Copy the production files from /home/ubuntu... to 
    /var/www/html

```
- name: Copy dist folder to /var/www/html
  run: sudo cp -r dist/* /var/www/html
```

**Just replace it with mine here:** 

```
name: Node.js CI

on:
  push:
    branches: [ "main" ]

jobs:
  build:

    runs-on: self-hosted

    strategy:
      matrix:
        node-version: [20.x]

    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: npm run build --if-present
    - name: Clear contents of /var/www/html
      run: sudo rm -rf /var/www/html/*
    - name: Copy dist folder to /var/www/html
      run: sudo cp -r dist/* /var/www/html
```
- **Commit this file** by clicking on the top right corner button which will trigger the workflow and nginx will serve the production version of the app. 

Click on the Actions tab again to watch magic happen!

Remember to `git pull` in your local repository as we committed the new workflow file to avoid divergent branches! After all, The love story shouldn't end on divergent branches. 💔

To check out the hosted app by going to the EC2 instance details. Look for `Public IPv4 DNS` and click on the link, but wait! Like all good things, it's wrapped in a layer of HTTPS we haven't quite set up yet. Fear not! **Simply remove the 'S' from HTTPS in the URL**, and voilà!

The URL should look something like: `http://ec2-35-177-142-238.eu-west-2.compute.amazonaws.com/`.

And just like that, our Vite app and EC2 are bound together. Whenever you push on main, the workflow kicks into action, redeploying our app to the world.

This story isn't over, in the next chapter we will discover HTTPS and SSL certificates.


Thank you, please like and subscribe! 💾
Voilà! If you enjoy this content, please consider supporting my efforts. Your generosity fuels more of what you love! 🩷
<a href="https://www.buymeacoffee.com/janoskocs" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a>

----------
I'm János, I write about Software, coding, and technology.
Checkout my [portfolio](https://janoskocs.com)

Cover photo by: [Alexander Sinn](https://unsplash.com/@swimstaralex)