Automatic Deployments With Nginx And GitHub Actions: A Detailed Guide
A pipeline for automating your deployment of web apps on a server with Nginx and custom domains!
Deployment comes up every now and then in the lives of us developers, whether we work with with web apps containing NodeJS (React, Next, Vue JS, etc), Python (Django, FastAPI, Flask) frameworks or anything else on a regular basis.
A typical workflow in the deployment pipeline will look like this:
You make some changes on your local machine
You commit and push it to your remote Git repository like GitHub or GitLab
You run a few tasks pertaining to formatting, testing, etc on your changed files
You finally push it to your server
And optionally, you also update some files on your server, perform some refresh/restart actions for the server to display the latest version of your web app.
These manual steps over time can become very tedious and needlessly time-consuming. I was quite surprised to not find a single thorough working guide on the internet to tackle this whole process so I decided to write and share my own.
The good news is that all of these steps can be automated with the help of a few tools and libraries which I’m going to show you in this article, so let’s dive in!
Setting up a React project
Here, I’m going to simply set up a basic React app with Vite. Vite is a better alternative than create-react-app for a variety of reasons.
We’ll keep the app simple for now because the agenda for this article is to look at configuring automatic deployments of the application rather than the architecture of the application.
I recently started working on building my personal website, and since I already had a domain registered, I decided to start with a basic project and keep building more from there.
In this tutorial, we’ll see how I built up a simple landing page of my website with a few links to my social profiles and we’ll build and deploy it.
You can go ahead and simply use a starter React project and follow this guide to deploy it!
The simple starter website that I’ve deployed is currently live at: yashprakash.com so you can view it :)
Let’s get started! In your new project’s root directory, just simply type and enter:
$ yarn create vite
Then follow the on-screen instructions to create a starter project. Currently, I only have a simple App.jsx in my project which you’ll be able to view in the project’s GitHub repository mentioned at the end.
Now, you can either build something there or skip right ahead to the deployment section.
Making a GitHub Actions Runner
After pushing our app’s code to a GitHub repository, you need to instantiate and host a new runner script which will help run the commands for our automatic deployment directly from GitHub.
You will need to host this runner on your remote server. So head on to a VPS provider if you haven’t already and book a cheap server. I’ve used GCP Compute Engine for this one.
Next, go to the Settings tab in your GitHub repository's home page:
And select the Actions ➡ Runners tab on the sidebar.
There you’ll find the green button to create a new self-hosted runner.
Select your server’s OS from the tabs and what you’ll get is a set of instructions to execute directly on your server.
Let’s move on to configuring the server next! :)
The Initial Ubuntu Server Setup
What you need first is an external IP address for the VPS that you reserved. Once you have that, you can SSH into the remote machine as root.
$ ssh root@your-ipaddress
Now, you want to do a few things to get started, the first of which is updating packages and then creating a new user and assigning it “sudo” privileges. Let’s go perform them:
$ sudo apt update
$ sudo apt upgrade
If you check with the whoami
command now, you’ll see you’ve logged in as root. Add new user with the following command:
$ adduser your-new-username
You will be asked for a password as well. Follow through with the instructions to get it set up.
You can use the command id your-new-username
to see the user info including the groups it belongs to currently. You want to include “sudo” amongst that group too.
$ usermod -aG sudo your-new-username
Now if you run the id
command, you’ll see the user has “sudo” added in parenthesis.
Now, you want this user to be able to also log in to your VPS via the same SSH key that you used for the root user. Doing this, we’ll finally be able to go ahead and do a couple of most important things when it comes to increasing security of our server:
log in as that new user instead of root every time
disable password login
SSH keys for the new user that you created need to be added to a file called authorized_keys
in that user’s home directory.
Go to the new user’s home directory first:
$ cd /home/your-new-user
Create a .ssh
directory and navigate into it:
mkdir .ssh
cd .ssh
Create a new file called authorized_keys:
touch authorized_keys
Now you want to put your public ssh key in that file. (Please only copy the SSH key with .pub
in its name at the end!!)
You can open it with a simple text editor called nano
:
$ sudo nano authorized_keys
Now you can paste your public key in here.
Once you’ve done that, hit Ctrl or Command + X
then hit Y
to save with the same name and hit enter / return
again to save.
Disabling password login is an extra security step that you can do: Open the sshd_config
file on your server:
$ sudo nano /etc/ssh/sshd_config
Look for where it says:
# PasswordAuthentication Yes
Remove the # if there is one and change the Yes to No to disable Password login. Now, simply restart the sshd
service for this to take effect:
$ sudo systemctl restart sshd
Very important: To test this setup, open a new terminal tab on your local machine (don’t close this one) and try to login as the new user.
$ ssh your-new-user@ip-address
If you see an error, it could be because of one or two reasons which may include us having to restart the
sshd
service, adding our ssh keys to the ssh agent and so on. Please follow this other guide that I wrote to debug that!
Now is the time to get started with configuring the GitHub Actions runner on the server, so let’s go!
Setup GitHub Actions Runner On The Server
Remember the new Runner that you setup in your project’s GitHub repository settings earlier? Yes, it is time to go run those commands on the server.
$ mkdir actions-runner && cd actions-runner
This is the folder the runner will live in, and the folder inside which your project will also be cloned into when you checkout the GitHub branch during your workflow.
But that’ll come later. Let’s get this done first.
Follow the instructions on the GitHub page up to the point where it says:
$ ./run.sh
If you run that, you’ll see that your Runner comes alive:
But as soon as you close it with Ctrl/Cmd + C, you’ll see that it goes offline. You don’t want that.
A ls
of the runner directory shows you that you have a .svc.sh
file as well. We need to install and run this so that our runner continues to run in the background.
Let’s do it:
$ sudo .svc.sh install
$ sudo ./svc.sh start
You’ll see that your runner comes online once again and you still have access to the terminal. Great!
Time to setup the Actions workflow file!
Writing The GitHub Actions Workflow File
The first thing you need to know about the workflow file is that it stays in the .github/workflows/your-workflow-file-name.yml
in your project’s root directory.
$ mkdir -p .github/workflows && cd .github/workflows
$ touch workflow.yml
Now, let’s start with making sure your workflow runs on every push to the master branch:
# Simple CD
name: push_cd
on:
push:
branches: ["master"]
jobs:
build:
runs-on: self-hosted
Now, you want to create jobs to do the following:
checkout your project’s Git repo onto your server
run commands for installing and then building with npm or yarn (I'm using yarn for this example)
Append this to your file:
jobs:
build:
runs-on: self-hosted
strategy:
matrix:
node-version: [19.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Run install
uses: borales/actions-yarn@v4
with:
cmd: install
- name: Build production bundle
uses: borales/actions-yarn@v4
with:
cmd: build
Your workflow file is ready to be deployed.
$ git add .
$ git commit -m "added workflow file"
$ git push
You’ll see an action running in the Actions tab of your GitHub repository:
Once complete, you’ll see it display a green checkmark:
Locate the app folder or whatever you named it on your server within the runner directory:
$ ls actions-runner
Perfect! You have now made sure your runner can read from and build your project on your server with any new changes that you push from your local machine!
Configuring Nginx On The Server
Now, you want to install and configure Nginx as the webserver to deploy your app:
$ sudo apt install nginx
If you check the status after installing it, you’ll see that it’s running already:
$ sudo systemctl status nginx
Now, go to your IP address on your browser and you will see a Welcome to Nginx! page displayed.
Now you need to configure the server block for your web app.
Open the Nginx config file:
$ sudo nano /etc/nginx/sites-available/default
Find the root label and replace with this:
root /var/www/build;
If you recognize build folder from the npm or yarn build command, you’re right on the money! That is exactly the folder we want Nginx to find!
Above that, you can also put the domain that you plan on using (if you do):
server_name yourdomain.com www.yourdomain.com;
Save and close the file and check your Nginx configuration with this command:
$ sudo nginx -t
Now restart the NGINX service:
$ sudo systemctl restart nginx
Now you should see your app when you go to your IP address in the browser!
Please note: If you’re setting up a custom domain, make sure to change your DNS records with the VPS provider you’re using!
Now comes the final step: making sure that your Nginx service gets the newly built React app on every push from your local machine.
Conjuring Visibility of Latest Changes Pushed
You’ll notice that you have a build or dist folder once you go into your actions-runner directory on your server. The full path will look something like this:
/home/username/actions-runner/app/website/website/build
Copy that path with the pwd
command.
Now, there’s one change that you want to do in your Actions Workflow file: copy over the latest build folder to Nginx directory that you supplied previously in root.
Add it:
- name: Copy folder to where nginx expects it to be
run: cp -r ${{secrets.COPY_FROM_FOLDER}} ${{secrets.COPY_TO_FOLDER}}
You’ll see two environmental variables here. Add them in your GitHub repository settings here:
Perfect! Once you push these changes via Git, you’ll see your app come alive with the latest changes!
Our automatic deployment pipeline is complete. Well done! 🎉
A few parting words…
Good job on following through with the tutorial to set up automatic deployments directly from your local machine to your remote server. This is one of the fundamental practises in DevOps and you just learned a whole lot of it here.
You’ll find the repository that I worked with here.
If you enjoyed this tutorial, share this newsletter with a friend maybe?