This tutorial has been specifically written for Ubuntu 16.4, although it may apply to other versions as well. For all commands and code examples, replace {{text}} with values specific to your project.

Create Server

If you haven't already, in your hosting company's control panel, create a new Server.

Note: Make sure to open Ports 80 and 443 on the Server Firewall.

Install Server Software

Become an administrator

sudo su

Prepare for installation

apt-get update
apt-get upgrade
apt-get autoremove

Install Node

curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
apt-get install -y nodejs
cd /etc
mkdir node

Install Nginx

apt-get install nginx

Install MongoDB (Optional)

Note: (Instructions are from https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/)

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 2930ADAE8CAF5059EE73BB4B58712A2291FA4AD5
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.6 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.6.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo service mongod start

Configure Server Software

Configure Nginx

cd /etc/nginx/sites-available/
touch {{site name}}
vim {{site name}}

Paste this into the file. Save by pressing ESC, and entering :wq.

server {
    listen 80;

    server_name {{site url}};
    charset UTF-8;

    location / {
        root /etc/node/{{site root}}/{{public folder}};
        try_files $uri $uri/ @dynamic;
        expires max;
        access_log off;
    }

    location @dynamic {
        proxy_pass http://localhost:{{site port}};
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Add GZip Compression to /etc/nginx/nginx.conf. Uncomment these lines, and update. This step is optional.

gzip on;
gzip_disable "msie6";

gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon;

Update Enabled Sites

ln -s /etc/nginx/sites-available/{{site name}} /etc/nginx/sites-enabled/{{site name}}
nginx -t
nginx -s reload

Prepare for Deployment

Create Deployment User

adduser deployment
usermod -aG sudo deployment
chown -R deployment /etc/node/
chgrp -R deployment /etc/node/
su deployment

Create project folder

mkdir -p /etc/node/{{site root}}

Create SSH Keys

cd /home/deployment
ssh-keygen -t rsa -C "deployment@{{site url}}" -b 4096

Store the output of these two commands for use

cat ~/.ssh/id_rsa.pub
cat ~/.ssh/id_rsa

Install SSH Keys

ssh-copy-id {{site url}}

Exit back to admin user

exit

Set Up Deployment

Create a GitLab account and Log In.

In GitLab, go to Profile -> Settings -> SSH Keys. Enter the value of ~/.ssh/id_rsa.pub into Key. Title should populate automatically. Click Add key.

Create a Project. In your project, go to Settings -> CI / CD. Expand Secret variables. Enter STAGING_PRIVATE_KEY into Key. Enter the value of ~/.ssh/id_rsa into Value. Click Add new variable.

Create GitLab Deployment

In your project, create the file .gitlab-ci.yml. Paste into that file:

image: node:latest

services:

cache:
  paths:
  - node_modules/

test_async:
  script:
   - npm install
   - npm test

before_script:
  - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
  - mkdir -p ~/.ssh
  - echo "$STAGING_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
  - chmod 600 ~/.ssh/id_rsa
  - eval "$(ssh-agent -s)"
  - ssh-add ~/.ssh/id_rsa
  - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'

staging:
  type: deploy
  script:
    - npm install
    - npm run dev
    - tar -zcf ../build.tar.gz ./ --exclude='.git/' --exclude='build.tar.gz'
    - ssh deployment@{{site url}} "
        cd /etc/node &&
        rm -rf _tmp &&
        mkdir -p _tmp/{{site root}}_new"
    - scp ../build.tar.gz deployment@{{site url}}:/etc/node/_tmp
    - ssh deployment@{{site url}} "
        cd /etc/node/_tmp &&
        tar -xzf build.tar.gz -C {{site root}}_new &&
        rm build.tar.gz &&
        mv /etc/node/{{site root}} {{site root}}_old &&
        mv {{site root}}_new /etc/node/{{site root}} &&
        cd ../ &&
        rm -rf _tmp"
  only:
  - master

Commit the file, and allow GitLab to deploy it.

Start Node Server

Install Node Site Runner

npm install -g pm2
su deployment
touch process.yml
vim process.yml

Paste this into the file. Save by pressing ESC, and entering :wq.

apps:
  - script: {{site root}}/{{main js file}}
    instances: 4
    exec_mode: cluster
    watch: true
    env:
      NODE_ENV: development
    env_production:
      NODE_ENV: production

Exit back to admin

exit

Create SSL Certificate

Create Certificate

apt-get install software-properties-common
add-apt-repository ppa:certbot/certbot
apt-get update
apt-get install python-certbot-nginx
certbot certonly --webroot -w /etc/node/{{site root}}/{{public folder}}/ -d {{site url}}
certbot --nginx

Renew Certificate

certbot renew