Bildquelle: ESA/DLR/FU Berlin; CC BY-SA 3.0 IGO (modified)

Hosting NextJS on a private server using PM2 and Github webhooks as CI/CD

By Max Dietrich | Geospatial specialist, Web-Developer.

Setup your server

First of all you need an server with root access. I strongly recommend to have a look at the guide "Initial Server Setup with Ubuntu 18.04" from the DigitalOcean community which will lead you through the process of:

  1. Logging and set up root user access to your server with SSH
  2. Creating a new user
  3. Granting Administrative Privileges to the new user
  4. Setting up a basic firewall
  5. Giving your regular user access to the server with SSH key authentication.

After you have done that you can continue by installing all necessary programs on your server.

Install Node.js

Again there is an guide by DigitalOcean which will help you installing Node.js using PPA.

After completing

  • install Node.js, NPM and
  • the "build-essential package"

you will have to change npm's default directory.

  • Create a .npm-global directory and set the path to this directory for node_modules:
cd ~
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
  • Create (or modify) a ~/.profile and add the following line:
sudo nano ~/.profile
# set PATH so global node modules install without permission issues
export PATH=~/.npm-global/bin:$PATH

Now you have to update your system variables:

source ~/.profile

Now you should be able to check your installed Node.js version with:

node -v

Install git

Check if git is already installed with:

git --version

If it isn't installed yet follow the guide How to install git on Ubuntu 18.04.

After git is installed you can deploy your project by cloning it from Github.

Deploy from Github

It is important that you are loggin in as non-root user for the following steps.

cd ~
git clone https://github.com/your-name/your-project-repo.git path

Create a .env on the server if you are using one locally and copy/paste your content.

After you have deployed your project (optionally with environment variables) you can install all dependencies and build your Next.js site with:

cd ./my-project/
npm install
NODE_ENV=production npm run build

Now you should have a copy of your local project/Next.js site on your remote server.

Next you are going to setup PM2 which will be used to keep your site alive and restart it after every reboot.

Setup PM2

You can install PM2 with:

npm install pm2@latest -g

You will need to create/configure an ecosystem.config.js file which will restart the default Next.js server.

cd ~
pm2 init
sudo nano ecosystem.config.js

Copy/paste the template and replace the content.

module.exports = {
  apps: [
    {
      name: 'next-site',
      cwd: ' /home/your-name/my-nextjs-project',
      script: 'npm',
      args: 'start',
      env: {
        NEXT_PUBLIC_...: 'NEXT_PUBLIC_...',
      },
    },
    // optionally a second project
],};

With

cd ~
pm2 start ecosystem.config.js

you can start your server which will run on the Port 1337.

You can always check the status with:

pm2 status next-site

After the server reboots this PM2 should be always automatically be restarted. For that you are going to need a small Startup script which you can copy/paste.

  • Generate and configure a startup script to launch PM2:
$ cd ~
$ pm2 startup systemd

[PM2] Init System found: systemd
[PM2] To setup the Startup Script, copy/paste the following command:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u your-name --hp /home/your-name
  • Copy/paste the generated command:
$ sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u your-name --hp /home/your-name

[PM2] Init System found: systemd
Platform systemd

. . .


[PM2] [v] Command successfully executed.
+---------------------------------------+
[PM2] Freeze a process list on reboot via:
   $ pm2 save

[PM2] Remove init script via:
   $ pm2 unstartup systemd
  • And save the new PM2 process list and environments. Then Start the service with systemctl.
pm2 save

[PM2] Saving current process list...
[PM2] Successfully saved in /home/your-name/.pm2/dump.pm2

If you reboot your server now with sudo reboot the script should be automatically restart your Next.js site. Give it a try!

Setup Github Webhook

One thing missing now is an continuos integration and continuos delivery (CI/CD) pipeline which you will setup using Github webhooks.

Therefore you need to create a new Webhook in your repository.

The following articles provide additional information to the steps below:

You need to create a server script which will do something if it is triggered by the Github webhook.

cd ~
mkdir NodeWebHooks
cd NodeWebHooks
sudo nano webhook.js

The script is going to create a server running on Port 8100. (Your Github webhook should be of course sending the webhook to something like http://server-ip:8100.)

If it gets triggered by a webhook it will

  • go into your repo ~/my-nextjs-project/,
  • pull the latest commits,
  • install all dependencies,
  • build a new version of the site and
  • restart the server via the PM2 script.
const secret = "your-secret-key";
const repo = "~/my-nextjs-project/";

const http = require('http');
const crypto = require('crypto');
const exec = require('child_process').exec;

const BUILD_CMD = 'npm install && NODE_ENV=production npm run build';
const PM2_CMD = 'pm2 restart next-site';

http.createServer(function (req, res) {
    req.on('data', function(chunk) {
        let sig = "sha1=" + crypto.createHmac('sha1', secret).update(chunk.toString()).digest('hex');

        if (req.headers['x-hub-signature'] == sig) {
            exec('cd ' + repo + ` && git pull && npm install && ${BUILD_CMD} && ${PM2_CMD}`);
        }
    });

    res.end();
}).listen(8100);

You will need to allow communication on Port 8100 with:

sudo ufw allow 8100/tcp
sudo ufw enable

Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

Earlier you setup PM2 to restart the services (your Next.js site) whenever the server reboots or is started. You will now do the same for the webhook script.

  • Run echo $PATH and copy the output for use in the next step.
echo $PATH

/home/your-name/.npm-global/bin:/home/your-name/bin:/home/your-name/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
  • Create a webhook.service file:
cd ~
sudo nano /etc/systemd/system/webhook.service
  • In the editor, copy/paste the following script, but make sure to replace your-name in two places with your username. Earlier, you ran echo $PATH, copy this to the Environment=PATH= variable, then save and exit:
[Unit]
Description=Github webhook
After=network.target

[Service]
Environment=PATH=your_path
Type=simple
User=your-name
ExecStart=/usr/bin/nodejs /home/your-name/NodeWebHooks/webhook.js
Restart=on-failure

[Install]
WantedBy=multi-user.target
  • Enable and start the new service so it starts when the system boots:
sudo systemctl enable webhook.service
sudo systemctl start webhook
  • Check the status of the webhook:
sudo systemctl status webhook

You can test your webhook with these instructions.

The Next.js server is now running and you implemented a CI/CD pipeline via PM2 and Github Webhooks but you still can't see your website because you need a domain and an webserver like Nginx.

Configure Nginx

I am using Cloudflare to manage DNS for my domains but you can do this with every other provider also.

  • Create two A Records which will point your-domain.com and www.your-domain.com to the IP-adress of your server.

After that you will need to configure Nginx.

The following instructions are based on How To Install Nginx on Ubuntu 18.04 [Quickstart].

  • Update your local package index:
sudo apt update
  • install Nginx:
sudo apt install nginx
  • and adjust the Firewall:
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'

You should now be able to see the Nginx landing page on:

http://your_server_ip

Setting up Server Blocks

  • Create the directory for your-domain.com, using the -p flag to create any necessary parent directories:
sudo mkdir -p /var/www/your-domain.com/html
  • Assign ownership of the directory:
sudo chown -R $USER:$USER /var/www/your-domain.com/html
  • The permissions of your web roots should be correct if you haven’t modified your umask value, but you can make sure by typing:
sudo chmod -R 755 /var/www/example.com
  • Make a new server block at /etc/nginx/sites-available/your-domain.com:
sudo nano /etc/nginx/sites-available/example.com
  • Copy/Paste the following configuration block and update the domain name:
server {
    # Listen HTTP
    listen 80;
    listen [::]:80;

    server_name your-domain.com www.your-domain.com;

    # Redirect HTTP to HTTPS
    return 301 https://$host$request_uri;
}

server {
    # Listen HTTP
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name your-domain.com www.your-domain.com;

    # SSL config
    include snippets/self-signed.conf;
    include snippets/ssl-params.conf;

    # Proxy Config
    location / {
        proxy_pass http://localhost:1337;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_pass_request_headers on;
    }
    location ~ /.well-known {
       allow all;
    }
}

Save the file and close it when you are finished.

  • Enable the file by creating a link from it to the sites-enabled directory:
sudo ln -s /etc/nginx/sites-available/your-domain.com /etc/nginx/sites-enabled/
  • Test for syntax errors:
sudo nginx -t
  • and finally enable the changes:
sudo systemctl restart nginx

Nginx should now be serving content on your domain name. That means if you have a look at http://your-domain.com you should see your Next.js site.

In the end should deny traffic to Port 1337 with:

cd ~
sudo ufw deny 1337

This guide is also using parts of Strapi Deployment on DigitalOcean which helped me a lot setting up Strapi and Next.js on a server in a proper way.

More Articles:

Syntax Highlighting with Prism and Next.js

Prism is a compact, expandable syntax highlighter that was developed with modern web standards in mind.

How to create a Mailchimp newsletter sign-up-form for your Gatsby Site

Managing your own newsletter is crucial for creating a sustainable online-business. With E-Mails you can build a relationsship with your audience and engage with them so they will drive some nice traffic to your new post or whatever you just have published and want to promote.

GatsbyJS with CI/CD Pipeline via Codebuild

With the free tier for AWS you always get one active AWS code pipeline per month and 100 minutes of AWS code build per month with which you can create a CI / CD pipeline for a GatsbyJS site.

I am Max Dietrich.

I am currently working as Geodata-Manager at RIWA.
I love to experiment with web-applications which i am documenting on this site.

Recent Articles

    Good Links

    • All Articles
    • Site Stats
    • About this site
    • Disclaimer & Imprint

    Connect

    You can connect with me on:
    Copyright © 2020 Max Dietrich. All Rights Reserved.
    Made with ❤️ by MXD.