DEV Community

loading...
Cover image for deploying node.js on digital ocean with pm2

deploying node.js on digital ocean with pm2

anthony-campolo
full stack web dev
Updated on ・8 min read

Serverless deployment is becoming easier and easier every year, but there will always be a subset of use cases that require a persistent, running server.

Projects with stricter requirements around performance, computation, storage, concurrency, and isolation may opt for a more traditional deployment strategy and host a Linux server.

In this tutorial we will deploy a Node.js application on Digital Ocean with PM2.

Outline

  1. Create a Node.js app
  2. Configure Node.js app for PM2
  3. Deploy Linux server on Digital Ocean droplet
  4. Setup SSH keys
  5. Install Node.js, Git, and PM2

1. Create a Node.js app

We will generate a simple Node.js application.

mkdir ajcwebdev-pm2
cd ajcwebdev-pm2
yarn init -y
touch index.js
Enter fullscreen mode Exit fullscreen mode

package.json

If we look at our package.json file in the root of our project we will see:

{
  "name": "ajcwebdev-pm2",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode

index.js

index.js will return a header and paragraph tag.

const http = require('http')

const hostname = '127.0.0.1'
const port = 3000

const server = http.createServer(
  (req, res) => {
    res.statusCode = 200
    res.setHeader('Content-Type', 'text/html')
    res.end('<h1>ajcwebdev</h1><p>PM2 is a daemon process manager</p>')
  }
)

server.listen(port, hostname, () => {
  console.log(`Running on http://${hostname}:${port}/`)
})
Enter fullscreen mode Exit fullscreen mode

Start development server

Enter the following command to start your development server and see your project.

node index.js
Enter fullscreen mode Exit fullscreen mode

Terminal output:

Server running at http://127.0.0.1:3000/
Enter fullscreen mode Exit fullscreen mode

Open localhost:3000

The file is served to localhost:3000

01-node-app-running-on-localhost-3000

Create GitHub repository

Create a repository on GitHub.

02-blank-github-repository

In your Node project initialize a Git repository.

git init
git add .
git commit -m "Cause I need a server"
Enter fullscreen mode Exit fullscreen mode

Make sure to set the remote to your own repository: https://github.com/{username}/{repo_name}.git.

git remote add origin https://github.com/ajcwebdev/ajcwebdev-pm2.git
git push -u origin main
Enter fullscreen mode Exit fullscreen mode

Verify that your project was pushed to main.

03-github-repository-with-project

2. Configure Node.js app for PM2

Install PM2 globally

You can use either yarn or npm.

yarn global add pm2
Enter fullscreen mode Exit fullscreen mode

Terminal output:

success Installed "pm2@4.5.6" with binaries:
      - pm2
      - pm2-dev
      - pm2-docker
      - pm2-runtime
Enter fullscreen mode Exit fullscreen mode

Create a PM2 ecosystem configuration file.

pm2 init
Enter fullscreen mode Exit fullscreen mode

Terminal output:

Runtime Edition

PM2 is a Production Process Manager for Node.js
applications with a built-in Load Balancer.

Start and Daemonize any application:
$ pm2 start app.js

Load Balance 4 instances of api.js:
$ pm2 start api.js -i 4

Monitor in production:
$ pm2 monitor

Make pm2 auto-boot at server restart:
$ pm2 startup

To go further checkout:
http://pm2.io/

[PM2] Spawning PM2 daemon with pm2_home=/Users/ajcwebdev/.pm2
[PM2] PM2 Successfully daemonized
File /Users/ajcwebdev/ajcwebdev-pm2/ecosystem.config.js generated
Enter fullscreen mode Exit fullscreen mode

ecosystem.config.js

module.exports = {
  apps : [{
    script: 'index.js',
    watch: '.'
  }, {
    script: './service-worker/',
    watch: ['./service-worker']
  }],

  deploy : {
    production : {
      user : 'SSH_USERNAME',
      host : 'SSH_HOSTMACHINE',
      ref  : 'origin/master',
      repo : 'GIT_REPOSITORY',
      path : 'DESTINATION_PATH',
      'pre-deploy-local': '',
      'post-deploy' : 'npm install && pm2 reload ecosystem.config.js --env production',
      'pre-setup': ''
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

Commit changes to GitHub.

git add .
git commit -m "pm2 lol"
git push
Enter fullscreen mode Exit fullscreen mode

That was the easy part. Here be servers.

3. Deploy Linux server on Digital Ocean droplet

There are many ways to host a Linux server, if you are comfortable with other providers you should be able to host this example project essentially anywhere you can host a Node server. We will create an account on Digital Ocean which provides $100 of free credits to get started.

04-digital-ocean-project

Click "Get Started with a Droplet" to get started with a droplet.

05-choose-an-image-and-choose-a-plan

Select Ubuntu 21.04 x64 and the Shared CPU plan.

06-choose-cpu-options

Select the cheapest option, Regular Intel with SSD and $5 a month.

07-choose-a-datacenter-region

We do not need block storage. Pick the datacenter region closest to your location.

08-authentication-ssh-keys

4. Setup SSH keys

Click "New SSH key" to enter a new SSH key.

09-add-public-ssh-key

SSH keys provide a more secure way of logging into a virtual private server than using a password alone.

Generate an RSA key pair on your local computer

There are several ways to use SSH; one is to use automatically generated public-private key pairs to simply encrypt a network connection, and then use password authentication to log on.

Another is to use a manually generated public-private key pair to perform the authentication, allowing users or programs to log in without having to specify a password.

ssh-keygen
Enter fullscreen mode Exit fullscreen mode

Terminal output:

Generating public/private rsa key pair.
Enter fullscreen mode Exit fullscreen mode

SSH is an authentication method used to gain access to an encrypted connection between systems with the intent of managing or operating the remote system.

SSH keys are 2048 bits by default. This is generally considered to be good enough for security, but if you think your 13 line JavaScript project might be a target for Advanced persistent threats you can include the -b argument with the number of bits you would like such as ssh-keygen -b 4096.

Enter file in which to save the key (/Users/ajcwebdev/.ssh/id_rsa): 
Enter fullscreen mode Exit fullscreen mode

This prompt allows you to choose the location to store your RSA private key. Press ENTER to leave the default which stores them in the .ssh hidden directory in your user’s home directory.

Create a password

Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Enter fullscreen mode Exit fullscreen mode

Terminal output:

Your identification has been saved in
/Users/ajcwebdev/.ssh/id_rsa

Your public key has been saved in
/Users/ajcwebdev/.ssh/id_rsa.pub

The key fingerprint is:
SHA256:s9sV2rydQ6A4FtVgq2fckCFu7fZbYAhamXnUR/7SVNI ajcwebdev@macbook.local

The key's randomart image is:
+---[RSA 3072]----+
|.oO.o   . ...    |
| = B + o o oE    |
|  = = . = =      |
| o = o + * o     |
|  = . + S = .    |
|   . o . O       |
|      o + o .    |
|       o +oo     |
|        +oo+.    |
+----[SHA256]-----+
Enter fullscreen mode Exit fullscreen mode

Copy your key to the clipboard

pbcopy < ~/.ssh/id_rsa.pub
Enter fullscreen mode Exit fullscreen mode

Paste the key into the SSH key content input and id_rsa.pub for the name input.

Choose a hostname

10-finalize-and-create-choose-hostname

In a minute or so your server will be created and deployed.

11-digital-ocean-server

Login to your server from your terminal

The username is root and the password is whatever you used when you created your server.

ssh root@143.198.110.139
Enter fullscreen mode Exit fullscreen mode

Terminal output:

The authenticity of host '143.198.110.139 (143.198.110.139)' can't be established.
ECDSA key fingerprint is SHA256:TDleCA22LIOrSx6QgJRWyUCOXoXXTqxOE4S8U8SHDAQ.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '143.198.110.139' (ECDSA) to the list of known hosts.
Enter fullscreen mode Exit fullscreen mode

Enter password

Enter passphrase for key '/Users/ajcwebdev/.ssh/id_rsa':
Enter fullscreen mode Exit fullscreen mode

Terminal output:

Welcome to Ubuntu 21.04 (GNU/Linux 5.11.0-17-generic x86_64)

* Documentation:  https://help.ubuntu.com
* Management:     https://landscape.canonical.com
* Support:        https://ubuntu.com/advantage

System information as of Wed Jun  2 17:46:53 UTC 2021

System load:  0.0               Users logged in:       0
Usage of /:   6.0% of 24.06GB   IPv4 address for eth0: 143.198.110.139
Memory usage: 16%               IPv4 address for eth0: 10.48.0.5
Swap usage:   0%                IPv4 address for eth1: 10.124.0.2
Processes:    90

The list of available updates is more than a week old.
To check for new updates run: sudo apt update

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described
in the individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law.
Enter fullscreen mode Exit fullscreen mode

5. Install Node.js, Git, and PM2

Let’s begin by installing the latest LTS release of Node.js, using the NodeSource package archives. First, install the NodeSource Personal Package Archive in order to get access to its contents.

Make sure you’re in your home directory, and use curl to retrieve the installation script for Node 12.

curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
Enter fullscreen mode Exit fullscreen mode

Terminal output:

Run `sudo apt-get install -y nodejs` to install Node.js 12.x and npm

You may also need development tools to build native addons:
  sudo apt-get install gcc g++ make

To install the Yarn package manager, run:
  curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/yarnkey.gpg >/dev/null
  echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
  sudo apt-get update && sudo apt-get install yarn
Enter fullscreen mode Exit fullscreen mode

Install Node.js on the server

sudo apt-get install -y nodejs
Enter fullscreen mode Exit fullscreen mode

Terminal output:

Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  nodejs

0 upgraded, 1 newly installed, 0 to remove and 16 not upgraded.
Need to get 18.1 MB of archives.
After this operation, 93.6 MB of additional disk space will be used.
Get:1 https://deb.nodesource.com/node_12.x hirsute/main amd64 nodejs amd64 12.22.1-deb-1nodesource1 [18.1 MB]
Fetched 18.1 MB in 0s (46.5 MB/s)
Selecting previously unselected package nodejs.
(Reading database ... 66333 files and directories currently installed.)

Preparing to unpack .../nodejs_12.22.1-deb-1nodesource1_amd64.deb ...
Unpacking nodejs (12.22.1-deb-1nodesource1) ...
Setting up nodejs (12.22.1-deb-1nodesource1) ...
Processing triggers for man-db (2.9.4-2) ...
Scanning processes...                                                                                                  
Scanning linux images...                                                                                               

Running kernel seems to be up-to-date.

No services need to be restarted.

No containers need to be restarted.

No user sessions are running outdated binaries.
Enter fullscreen mode Exit fullscreen mode

Check Node version.

node --version
Enter fullscreen mode Exit fullscreen mode

Terminal output:

v12.22.1
Enter fullscreen mode Exit fullscreen mode

Clone Git repo onto the server

git clone https://github.com/ajcwebdev/ajcwebdev-pm2.git
Enter fullscreen mode Exit fullscreen mode

Install pm2 to run app as a process

cd ajcwebdev-pm2
npm install -g pm2
Enter fullscreen mode Exit fullscreen mode

Terminal output:

/usr/bin/pm2 -> /usr/lib/node_modules/pm2/bin/pm2
/usr/bin/pm2-dev -> /usr/lib/node_modules/pm2/bin/pm2-dev
/usr/bin/pm2-docker -> /usr/lib/node_modules/pm2/bin/pm2-docker
/usr/bin/pm2-runtime -> /usr/lib/node_modules/pm2/bin/pm2-runtime

+ pm2@4.5.6
added 175 packages from 194 contributors in 12.376s
Enter fullscreen mode Exit fullscreen mode

Start pm2

pm2 start index.js
Enter fullscreen mode Exit fullscreen mode
[PM2] Spawning PM2 daemon with pm2_home=/root/.pm2
[PM2] PM2 Successfully daemonized
[PM2] Starting /root/ajcwebdev-pm2/index.js in fork_mode (1 instance)
[PM2] Done.
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ index              │ fork     │ 0    │ online    │ 0%       │ 30.4mb   │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘
Enter fullscreen mode Exit fullscreen mode

Discussion (2)

Collapse
nheindev profile image
Noah Hein

Ah yes, the digital ocean PM2 rabit hole. I know thee too well.

Collapse
ajcwebdev profile image
anthony-campolo Author

This isn't the kind of blog post you write because you want to, you write it because you have to.