In this blog post, I will share with you the exact steps, I’ve taken to deploy my website to a VPS and automated it, with help of Github Actions and some pitfalls I've encountered along this deployment process.
If you’ve not yet deployed your next.js website to your VPS, you can check How I deployed My Next.js website to a VPS using Nginx reverse proxy.
Before we start
Next.js is a production-grade React framework, made by Vercel to build static and dynamic websites and web applications.
You can use it for frontend or full-stack by using its API routing system which is provided out of the box.
As you may already know, Vercel is a service provided on top of cloud hosting. And meant for front-end developers.
Vercel makes it very easy to deploy your Next.js website or other frontend web technologies like Gatsby and VueJS.
Where am I going with this? All of this is to help you understand why I choose VPS and not Vercel.
Why I used VPS?
About 6 months ago, I was planning to use Laravel for my website, so I purchased a VPS from Hostinger, with 1vCPU, 1GB RAM, and 20GB SSD.
This is a good VPS choice to get started, after a little while, I’ve changed from Laravel to Nuxt.js. then tried Next.js and fell in love with it the same way I did with Laravel.
After about 6 months of trying different things, changing from one codebase to another, and trashing a lot of code.
Finally launched my website w3novices.com (which is now become codersteps.com) as a static blog and a learning resource, using Markdown as my data source.
So I’ve decided to use the VPS which I already own from Hostinger. I'm not expecting a lot of traffic this next 6 months, so my VPS plan is enough.
Also, I have no liquid at the moment, so all I have to invest is a little free time.
Let’s Get Started
I needed a simple way to keep my main
branch up to date with my production server.
So instead of connecting to my server each time, a new change was made, running the same commands each time, I decided to find a way to automate it.
So that’s what I did, I found GitHub workflow to be what I was looking for and satisfied my needs.
Making the GitHub workflow do what I want is a little cloudy at first, but after the first time, It will be a straightforward process.
No worries, I will share with you each step with any external resources needed that you may need.
Creating a Deployment Workflow
A workflow is a YAML configuration file that instructs Github to deploy a branch
to your server on a given event.
In our case, we will be using the push
event, so whenever we push a new commit to our main
branch our workflow will run.
Github Workflow runs as a job, this job may succeed or may fail, it may fail if, for example, it couldn’t ssh to your server or a command throws an error while executing.
Also can succeed if everything went as expected, some commands like git pull
may fail and the workflow job can succeed, I think because it doesn’t throw a system error.
To create a Github workflow we need to create a new file inside .github/workflows/ssh-deploy.yaml
from the root of our project.
The ssh-deploy.yaml
filename is what I choose for this situation, but feel free to change it whatever you prefer.
To learn more about Workflow syntax, if for any reason you want to extend this configuration file, you can check Workflow syntax for Github Actions.
Generating new ssh keys
I created an ssh key pair using ssh-keygen. Github has great docs to generate ssh keys.
It will select your computer operating system by default, so make sure you change it to Linux or any other os as your VPS server.
In my case, I give the ssh key a custom name, so it's only used with my website repository.
If you already have a pair of ssh keys on your server and want to use them, no need for this step, you can check inside the ~/.ssh
directory.
After the keys are generated successfully, it's time to add the public key to the authorized_keys, so the private key can be trusted.
It's an easy step, first cat ~/.ssh/id_keyid.pub
copy the output, then add it to the end of the ~/.ssh/authorized_keys
file, using vim
, nano
or whatever you prefer.
Setting up deploy keys
I set up Github Deploy Key for my repository so I can pull new changes by running "git pull" from the server using ssh, you can check Github Managing deploy keys docs.
The main idea behind deploy keys is that you provide your GitHub repository with your server public key and that public key needs to be in your server’s ssh-agent.
You can use the ssh public key you generated in the previous step.
Adding Github Secrets
Now that we have an ssh key, we need to store it somewhere safe, that’s what GitHub secrets are for.
You can learn how to add them simply by checking Github Encrypted secrets docs.
I added the following secrets:
SSH_PRIVATE_KEY
: the private key of the ssh I created, to get it, just run cat ~/.ssh/id_keyid
without .pub
extension, and change the ssh key filename. but if you choose the default, then it will be cat ~/.ssh/id_ed25519
SSH_USERNAME
: The username related to your server, if you don’t know, just run whoami
.
Connecting to the server through SSH
It's time to connect to the VPS from Github Actions and to do that we need to use a prebuilt Github Action called Install SSH Key.
Using this action we’ll be able to connect to our remote server through ssh. With Install SSH Key you can also use a username and password to connect to your server, but I prefer to use ssh.
Time for CI/CD Automation
After I successfully connected to my server, using Install SSH Key, it's time to start running some commands for some automation.
Create a YAML configuration file /repository-root-path/.github/workflows/ssh-deploy.yaml
If you haven't already.
name: Deployment Workflow
on:
push:
branches: [main]
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: SSH to server
uses: appleboy/[email protected]
with:
host: w3novices.com
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: 22
script: |
cd /path/to/your/repository/root
eval `ssh-agent -s`
ssh-add ~/.ssh/id_keyid
git pull origin main
git status
export PATH=/home/${{ secrets.SSH_USERNAME }}/.nvm/versions/node/v14.17.0/bin:/usr/bin:/bin;
yarn install
rm -rf .next
yarn build
pm2 restart yourappname
Because, I deployed my Next.js application a few times before thinking about automating it, I know the deployment process.
First, we registered a push event on the main
branch, so once we push a new commit to the branch this workflow will be executed.
Then, we created a job that will connect to our VPS using the appleboy/ssh-action
, then run the deployment process using the provided script.
Deployment script
In the provided YAML configuration above, we added a script
key, with the commands we want the job to execute on our server.
Here's the same script, with comments to help you understand what's going on, so you can adapt it to you're VPS and Next.js project.
cd /path/to/your/repository/root # navigate to Next.js project (absolute path)
eval `ssh-agent -s` # start ssh-agent
ssh-add ~/.ssh/id_keyid # depends on ssh-agent, add our private ssh to the ssh-agent, this is required for our VPS to communicate with Github
git pull origin main
git status
export PATH=/home/${{ secrets.SSH_USERNAME }}/.nvm/versions/node/v14.17.0/bin:/usr/bin:/bin; # adding yarn to the path so we can use it - you can use npm or pnpm
yarn install
rm -rf .next # make sure there's no cache from previous deployment
yarn build
pm2 restart yourappname # restart our next js server using PM2, change it with your setup
Don't forget to change /path/to/your/repository/root and id_keyid with your own, make sure you have Yarn installed, and make sure to change the node version.