How I Integrated a Simple CI/CD for Nextjs website on a VPS

Abdessamad El Hamdany
Abdessamad El Hamdany on
How I Integrated a Simple CI/CD for Nextjs website on a VPS

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 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 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 master 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 master 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.

A little setup beforehand

1. 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.

2. 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.

3. 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, change the ssh key filename, 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.

4. 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.

5. 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.

Now it's time to create that YAML configuration file /repository-root-path/.github/workflows/ssh-deploy.yaml If you haven't already.

Because I deployed my next.js application a few times before thinking about automating it, I kind of know the process of deployment.

The following are the steps described on the workflow configuration file:

  1. listen on push event to the master branch
  2. created a job that does the deployment process, as follow:
    1. connect to the server using Install SSH Key action
    2. cd /path/to/your/repository/root
    3. git pull origin master
    4. git status
    5. export PATH=/home/${{ secrets.SSH_USERNAME }}/.nvm/versions/node/v14.17.0/bin:/usr/bin:/bin;
    6. yarn install
    7. yarn build
    8. pm2 restart yourappname

This is the full version of my GitHub workflow configuration file:

name: Deployment Workflow
on:
  push:
    branches: [master]

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
      - name: SSH to server
        uses: appleboy/ssh-[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 master
            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

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 node version.

See you in the next one 😉