PM2 to the rescue

24/12/2020 07:23 • ☕️ 7 min read

First, we need to ask what is PM2?

So, in their makers own words: “PM2 is a daemon process manager that will help you manage and keep your application online” or in other words it is an app which is used to keep your Node.js application server (web app) alive even in the case when something goes wrong as application crash server is restarted and similar.

PM2 has: load balancer, logs facility, startup script, microservice management and many other easily accessible functionalities necessary for the production environment.

Considering that you already have a web application up and running let’s switch to PM2, if not there is a tutorial on how to create an Express.js web app running on Nginx you can quickly go through.

Basic Setup

So, to install PM2 run the following:

$ npm install -g pm2@latest

After PM2 is installed, a new folder .pm2 will appear in our /home/<user> folder. In it there will be logs, and other PM2 files. Remember this we will need it later.

Being globally available (-g flag), at your exposal there are the following commands:

From within the folder of your application you can simply start your application by typing:

$ pm2 start app.js -i 0

To check all running processes you can use:

$ pm2 list

which will give you something like: Process list

To get application console logs and errors we can use:

$ pm2 logs

To monitor a process, which will give us more details:

$ pm2 monit

It has a bit more functionality and it is possible to see logs, but those logs are not persistent as soon as we switch to other process logs will be wiped out, generally used to replicate some issue.

To stop all applications:

$ pm2 stop all

To delete all currently running processes, which is useful if you running your apps from the ecosystem file:

$ pm2 delete all

It is possible to delete a specific process using its name:

$ pm2 delete letit.buzz

And of course if processes are stopped we can use the following command to run them again:

$ pm2 start all

This, apart of start-up we will cover later, above is enough for the basic scenario, so now let explore a bit more.

A Little Bit More Complex Setup

Usually, when we are running in production, we will need to deal with multiple applications running at the same time, and each application will have different requirements, so let’s explore a more complex scenario.

First of all, it is out of the question to run all the time a bunch of Linux commands, so we will need to automate a process we need some kind of config.

If we run:

$ pm2 ecosystem 

it will generate an example ecosystem.config.js in our case we do not need deploy config block for now - we will cover it some other time.

I would like to emphasize that location from where you are running pm2 and where the ecosystem file is placed is important. The general advice is to keep an ecosystem file in the root of the web content /var/www.

module.exports = {
    apps: [
        {
            name: 'letit.buzz',
            script: './letit.buzz/app.js',
            args: 'LetITbuzz',
            instances: 1,
            exec_mode: 'fork',
            env: {
                NODE_ENV: 'development',
                PORT: 8007,
            },
        },
        {
            name: 'abc.com',
            script: './abc.com/app.js',
            args: 'ABC-1',
            instances: 2,
            watch: true,
            watch_delay: 1000,
            ignore_watch: ['.git', 'node_modules'],
            exec_mode: 'cluster',
            env_production: {
                NODE_ENV: 'production',
                PORT: 8005,
            },
            env: {
                NODE_ENV: 'development',
                PORT: 8005,
            },
        }
    ]
}

Let’s see what is what.

  • name - is the process name it will help you to locate the application in the pm2 list or to manipulate it if needed by pm2 start/delete....
  • script - is the location of your application, when you navigate it is from the location of your ecosystem file.
  • args - can be useful if you want to pass arguments to your node.js application that you can handle with line like const [,,firstArgument] = process.argv; Except passing arguments using ecosystem file it is possible to do it directly using the following:

    $ pm2 start app.js -- aa bb cc
    $ pm2 restart app.js -- 11 22 33

    as described here.

  • exec_mode - by default application runs in ‘fork’ mode but if you want to utilize the processing power of your server use ‘cluster’ mode then adjust next parameter.
  • instances - if you choose ‘max’ or ‘0’, PM2 will create maximum threads/cores available, any other number will create an equal number of instances of your application.
  • env - another way to pass arguments on the start to the application is with environment variables, except using Linux command export NAME=VALUE is to adjust them in the ecosystem file. Whatever we set in the env: block will be directly accessible from JavaScript code by using the following pattern process.env.PORT, process.env.NODE_ENV. It is possible to create an option block as env_production, env_something but those will not be directly accessible from our node.js app. The only way to use them is to pass the desired environment on the start as pm2 start ecosystem.config.js --env production.

If there is a requirement to pass multiple values, maybe the good option would be to use the .json file

For instance, we could create a config.json file with:

{
   "dev": {
        "psql": {
            "connection": "host=localhost port=5432 dbname=buzz user=john"
        },
        "redis": {
            "hosts": ["localhost"]
        }
   },
   "production": {
        "psql": {
            "connection": "host=192.168.7.11 port=5432 dbname=buzz user=admin"
        },
        "redis": {
            "hosts": ["8.8.8.8"]
        }
   }
}

Next inside of JavaScript file we can consume json by loading specific values:

var config = require('./config.json')[process.env.NODE_ENV || 'dev'];
db.connect(config.psql.connection, config.redis.hosts);

for the given NODE_ENV value we would set via shell:

export NODE_ENV=staging pm2 start app.js

or changing values in ecosystem within env config block as:

module.exports = {
    apps: [
        {
            ...
            env: {
                NODE_ENV: 'dev',
            },
  • watch - this is very similar to watch options with nodemon where overall idea is to track code changes, so after the new deployment process will automatically restart the newly updated application. Along with watch: true we need to set ignore_watch: ['.git', 'node_modules'] parameter which will ignore unnecessary files in our application folder. Lastly, there is watch_delay which is I guess (in lack of documentation) a number in milliseconds that will delay restart after a change. This can be useful if we copying multiple files.

Except defining these in the ecosystem file it is possible to pass them inline as:

$ pm2 start env.js --watch --ignore-watch="node_modules"

Startup

Now, when we know how to setup multiple applications and how to run pm2, it is only left to see how to handle an unexpected server reboot.

Type the pm2 startup command without sudo and it will spill out exactly what you need to execute next:

$ sudo env PATH=$PATH:/home/<user>/.nvm/versions/node/v12.7.1/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u <user> --hp /home/<user>

<user> will be the user you are using to run the applications. Copy and paste that entire line as you have it in your SSH press enter. After this you need to save the current pm2 process list with:

$ pm2 save

You will get the following message saying that the dump.pm2 file is created.

[PM2] Saving current process list...
[PM2] Successfully saved in /home/<user>/.pm2/dump.pm2

The dump file and all the log files will be in the ~/.pm2 folder.

If there is a need to update Node.js to a newer version or update the PM2 startup script use $ pm2 unstartup systemd and then repeat the above process.

Only left is to test that your application will run after a reboot, for that one you can use:

$ shutdown -r now

! Important: When you rebooting, pm2 will not restart from your ecosystem.config.js file but from the dump.pm2 file, replicating processes as they were. That being said it is important to remember when you make changes to the ecosystem file you will need to delete all processes start pm2 with the ecosystem file.

$ pm2 delete all 
$ pm2 start ecosystem.config.js.

In the end, you will need to pm2 save again to capture the current state of processes crating a new dump file.

Next time I will try to cover deployment and logging with pm2 until then there are few resources you can check:

Published: 24 Dec 2020

Any sufficiently advanced technology is indistinguishable from magic.
Aleksandar Ristevski on Twitter