I am Learn.i.ng

Deploying Node.JS application with PM2

Posted:    Updated:   by Afolabi

A process manager ensures that your application runs efficiently on the server. In this post, I will explain how to deploy your Node.JS project to production using PM2. PM2 is a daemon process manager with a built-in load balancer that keeps your application running even when you are not logged into the server.

Prerequisites

  • A Node.JS project
  • Linux-based web server. I recommend using Ubuntu although the screenshots used are from my MacOS computer
  • Root access on the Linux web server
  • Copy the Node.JS project to the server

Installations

  • Node.JS (18.x)
    curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - &&\ sudo apt-get install -y nodejs
  • Build essentials
    sudo apt install build-essential
  • PM2
    sudo npm install pm2 -g

Confirm successful Node.JS installation with node -v and npm -v. The outputs should show you the version of Node.JS and NPM you have installed

Next, configure PM2 to run on server restart with pm2 startup
It will return the appropriate command to run based on your operating system and username.

Below is an example command for a server running Ubuntu 22.04 with user ubuntu:

sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu

Starting the application

You can simply run pm2 start app.js

Note: If the project is written in TypeScript, you have to use the JavaScript version of the entry file stored in your output directory. This means that if your output directory is dist and your entry file is at src/main.ts, you will run pm2 start dist/src/main.js
This is the default path for NestJS apps.

one app running in fork mode with default script name

You can pass in optional flags to configure the application. For example, use pm2 start ./dist/src/main.js --name=User-Service-API to change the default name of the app

two apps running in fork mode, one has a custom name set

Here are some more options from the quick start page:

--watch
Watch and Restart app when files change
--max-memory-restart <200MB>
Set memory threshold for app reload
--log 
Specify log file
-- arg1 arg2 arg3
Pass extra arguments to the script
--restart-delay 
Delay between automatic restarts
--time
Prefix logs with time
--no-autorestart
Do not auto restart app
--cron 
Specify cron for forced restart
--no-daemon
Attach to application log

Note: Run the start command in the directory with .env file otherwise your environment variables validation will fail

PM2 modes

PM2 has two modes: fork and cluster.
fork is the default mode in which just one instance of the application runs
cluster mode uses Node.js cluster mode to scale out and automatically load-balance your application.
You can specify the number of instances you want with the -i (instances) option.

To start your Node.js app in cluster mode, run
pm2 start ./dist/src/main.js -i max --name=<app_name>

The possible instance values are:

  • 0/max to spread the app across all CPUs
  • -1 to spread the app across all but one CPU
  • number to spread the app across the specified number of CPUs

app with eight instances running in cluster mode

Running CRON jobs in cluster mode

When running PM2 in cluster mode and you have CRON jobs in your code, you should watch out for this: your CRON jobs will be replicated and executed on all the running instances of that application.

For example, if you have a job that credits a user’s wallet with $10 every morning and you start the application in cluster mode with pm2 start ./dist/src/main.js -i 4 --name=<app_name>, the user will be credited with $40 because there are 4 instances of the application and each of them credited the wallet with $10

console output from four cron jobs because an instance was not specified to run jobs on

Solution 1:

Move your CRON jobs into a separate worker application then run it in fork mode in PM2

  • Start main application in cluster mode:
    pm2 start ./dist/src/main.js -i max --name=<app_name>
  • Start worker application in fork mode:
    pm2 start ./dist/src/worker.js --name=<app_worker_name>

eight instances of one app running in cluster mode with an instance of worker running in fork mode

Solution 2:

Use PM2’s environment variables

Create an ecosystem.config.js file in the root directory of your application
. Paste the following into it:


module.exports = {
 apps: [
  {
   name: "Wallet-Service",
   script: "./dist/src/worker.js",
   instances: 4,
   exec_mode: "cluster",
   instance_var: "INSTANCE_ID",
   env: {
    PORT: 3000,
    NODE_ENV: "development",
   },
  },
 ],
};

Then run pm2 start ecosystem.config.js

four instances of one app in cluster mode started with ecosystem.config.js

Now, you can write your CRON job’s logic like this:


// run CRON job on the second instance in the cluster
if (parseInt(process.env.INSTANCE_ID) == 2) {
// CRON job’s logic here
}

This produces this result:

console output from cron job executed on specified instance in the cluster

pm2 commands

Use the following commands to manage processes:

pm2 list Shows all processes
pm2 desc Describes specified process
pm2 save Freezes a process list for automatic respawn. Accepts optional –force flag
pm2 logs Shows a combined log for all applications
pm2 logs Show log for the specified process. Use --line to customize the number of lines of the log to return
pm2 restart Kill and restart the process(es)
pm2 reload Zero-downtime reload on the specified process(es)
pm2 stop Stops process(es)
pm2 delete Deletes process(es)
pm2 monit Monitors all processes
pm2 examples Shows common usage examples
pm2 —help Shows the help menu

I will write about PM2 configuration using the ecosystem.config.js file and deploy a Golang app with PM2 in the future.

Further readings

  1. PM2 Quick Start
  2. PM2 cluster mode
  3. Node.js cluster mode
  4. PM2 environment variables
  5. PM2’s single page documentation