Skip to content

Latest commit

 

History

History
438 lines (299 loc) · 12.2 KB

nodejs-pm2-zero-downtime.md

File metadata and controls

438 lines (299 loc) · 12.2 KB

Zero Downtime Node.js Deployment using PM2

Why?

We want to deploy and run our Node.js app on a generic "cloud" hosting provider Virtual Private Server (VPS) instance.

PM2 allows us to simplify continuous deployment from our chosen CI service and have more fine-grained control over how our App is run.

## What?

Use PM2 to deploy your Node.js App to a Digital Ocean Virtual Private Server (VPS) instance with built-in monitoring, great service quality and minimal cost.

In this guide we will be using the following:

  • Digital Ocean Droplet (Virtual Private Server "VPS")
  • CentOS (Operating System) - though any "mainstream linux" will work, and Ubuntu/Debian is the most popular.
  • PM2 Process Manager.
  • LetsEncrypt Free SSL Certificates.

If you do not already have a Digital Ocean account, please use the following link to register: https://m.do.co/c/29379863a4f8 and get $10 in Credit.

## Who?

Developers who have out-grown Heroku (pricing) and want to deploy to a different (affordable) hosting/cloud provider.

Note: Heroku has many useful features including Logging, Review Apps, permissions and teams which easily justify the cost. But if you have reached $100/month you should consider switching, provided you understand that you will need to work for it!

How?

These are step-by-step instructions, follow them in order and don't skip steps!

0. Pre-requisites

Before we start, please ensure you have the following:

### 1. Create the DigitalOcean Instance

Go to: https://cloud.digitalocean.com/droplets/new and Create a Droplet!

Note: We are using a "blank" instance as opposed to a "One-click app", because this will show us how to setup "from scratch" and will thus be applicable to any cloud provider.

The instance we are creating is a CentOS 7.5 Droplet with 1GB RAM.

do-1-create-instance

Select your desired region (datacenter); (pick the nearest to your users or dev team) e.g:

do-2-select-region

The default hostname for the instance (based on the selected options) is: centos-s-1vcpu-1gb-lon1-01 let's change that to: centos-nodejs-pm2 so that we know what the instance does from reading it's hostname.

do-3-finalise

Click Create and wait for the instance to be created.

2. Login to the Instance via SSH/Console

Get the instance's IP (v4) Address, e.g: 206.189.26.154 from the UI: do-4-instance-ip-address

and run the following command into your terminal to login to the instance via SSH:

ssh root@206.189.26.154

While logged in as root run the update command:

yum update -y

There are security updates. Wait for the updates to run until you see "Complete!":
centos-update-complete

3. Install Node.js

#### 3.1 Install NVM

Install Node Version Manager (NVM) from GitHub:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash

enable the nvm command line tool by running the command:

source ~/.bashrc

3.2 Install the Latest Node.js Version (LTS)

Using NVM, install the latests (LTS) version of Node.js:

nvm install --lts

4. Install PM2 Globally

We install PM2 globally so that it can be used on the server.

npm install pm2 -g

You should see: pm2-installed-successfully

5. Install Git

Install Git CLI so that we can get the latest code from GitHub:

sudo yum install git -y

git-installed

6. Add Deployment SSH/RSA Public key

In order to use PM2 as our deployment tool we need to add the deployment key to:

  1. The "target" server. In this case we will upload both the public key to the server so that TravisCI can access the server via SSH and the private key so that the VPS can access GitHub to git pull the latest version of the code.
  2. GitHub as a "deploy key". The public needs to be added as a "deploy key" for the repo so that GitHub will accept a git pull request from the server.

If you don't already have a deployment key, see: encrypted-ssh-keys-deployment.md

6.1 Add the Deployment SSH Key the Server

Add the deployment SSH public key to the list of authorized_keys on the VPS:

vi /root/.ssh/authorized_keys

Add the private key to the server at ~/.ssh/deploy_key:

then set the permissions on the key:

chmod 400 ~/.ssh/deploy_key

6.2 Add the Public Key to the Deploy Keys for the Repo on GitHub

Follow the instructions in: https://developer.github.com/v3/guides/managing-deploy-keys/#deploy-keys and add the deploy_key to your project's "Deploy Keys" on GitHub. e.g: https://github.com/nelsonic/hello-world-node-http-server/settings/keys

You should see:

image

7. Deploy The App

Ensure that your App's project/repo has a ecosystem.config.js file.

Deploy the app using PM2 from your localhost:

pm2 deploy ecosystem.config.js production setup
pm2 deploy production exec "pm2 reload all"

pm2-deploy-success

PM2 will deploy the app on the default port (3000).
You can view the app by visiting: http://206.189.26.154:3000/

app-deployed

Checkpoint! This is our first sign of "success" but not a something we are going to send/show to end-users.

8. Install NGINX

Install NGINX so that we can run it as a Proxy for our Node.js App. This will allow us to both HTTPS and multiple Apps on the same server listening on TCP port 80/443.

sudo yum install epel-release -y && sudo yum install nginx -y

image

Start NGINX with the command:

nginx

When you visit http://206.189.26.154 you should see:

nginx-running

This is our second "checkpoint".

9. Add a proxy_pass directive to nginx.conf

In order to use NGINX as a proxy for our Node.js App, we need to setup a "proxy pass".

Edit the default nginx.conf file:

vi /etc/nginx/nginx.conf

In the nginx.conf file, locate the section that starts with location / { ... e.g:

server {
    listen       80 default_server;
    listen       [::]:80 default_server;
    server_name  _;
    root         /usr/share/nginx/html;

    # Load configuration files for the default server block.
    include /etc/nginx/default.d/*.conf;

    location / {
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}

Change:

location / {
}

To:

location / {
   proxy_pass http://localhost:3000;
}

Stop and re-start nginx:

pkill nginx
nginx

Now when you visit http://206.189.26.154 you should see:

app-running

Now we are getting closer to something we can show an end-user; no TCP port in the URL.

10. Update the App on Localhost

Make an update to your app on your localhost e.g: change "Hello World!" to "Hello PM2!"

E.g: https://github.com/nelsonic/hello-world-node-http-server/commit/5107062aad2b697b968180ec6de202d7e57e4a1a

11. Re-Deploy

Re-deploy the app from localhost using the commands:

pm2 deploy ecosystem.config.js production update
pm2 deploy ecosystem.config.js production exec "pm2 reload all"

Now when you visit http://206.189.26.154 you should see:

app-updated

12. Deploy From Continuous Integration

Luckily deploying the App from Travis-CI is quite straightforward:

language: node_js
node_js:
- node

before_install: # setup the RSA key for use in Dokku Deployment:
- openssl aes-256-cbc -K $encrypted_77965d5bdd4d_key -iv $encrypted_77965d5bdd4d_iv
  -in deploy_key.enc -out ./deploy_key -d
- eval "$(ssh-agent -s)"
- chmod 600 ./deploy_key
- echo -e "Host $SERVER_IP_ADDRESS\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config
- ssh-add ./deploy_key

after_success:
  - npm install pm2 -g
  - pm2 deploy ecosystem.config.js production update
  - pm2 deploy ecosystem.config.js production exec "pm2 reload all"

Example: .travis.yml

Sample output: https://travis-ci.org/nelsonic/hello-world-node-http-server/builds/446453771#L613

travis-ci-example

13. App on Subdomain

Login to your Domain Name Service and create a subdomain for your app:

pm2.dwyl.io

Once you have added that, go refill your water glass/bottle while you wait for the DNS to propagate.

Create the pm2_dwyl_io.conf file:

vi /etc/nginx/conf.d/pm2_dwyl_io.conf

And paste the following:

server {
    server_name pm2.dwyl.io;
    listen       80;
    root         /usr/share/nginx/html;

    location / {
      proxy_pass http://localhost:3000;
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}  

Restart nginx:

nginx -t
# if the config test works run:
pkill nginx
nginx

Now when you visit your subdomain in your browser, e.g: http://pm2.dwyl.io
you should see your app being served on the subdomain: image

Recommended Further/Background Reading