PM2: Node JS Process Management
Managing your application in a production environment can be complex since you need to make sure the app is always on, even after restarting down the server, you need ensure you have proper logs that can be accessed anytime, and you even need to ensure that bringing up an app that has been down is easy and fast.
In this tutorial, we will look at what PM2 is, how to install it and start using it, how to create custom configuration for your apps and keeping them alive even after a server restart, and more.
Prerequisites
In order to follow the tutorial, you need to have the following:
- Linux Machine (I am using Ubuntu 22.04)
- Latest Node JS on the Linux Machine (I am using v21.1.0)
Installation
Installing PM2 is pretty straightforward. All you have to do is the run the command below to install PM2 globally using NPM:
npm install pm2 -g
Setup
We will create a simple Node JS app to work on it through PM2.
First, create a file title server.js
, and inside it, add the following:
const http = require("node:http")
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World')
}).listen(8000)
console.log("Accepting requests on port 8000");
All the above code does is start an HTTP server that listens on port 8000 and return returns “Hello World” on requests.
Starting The App
When trying to run a Node JS app, you would do so by running node
along with the name of the main file, like so:
node server.js
The downside to this is that you need to keep the command running in the foreground. If you want to run other commands then you would have to open another terminal.
To keep it alive while in the background, we can use PM2 by running the following command:
pm2 start server.js
Upon running this command, you will get an output similar to the one below, then you will be back to the terminal:
[PM2] Done.
┌────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ single │ default │ N/A │ fork │ 73731 │ 0s │ 0 │ online │ 0% │ 1.4mb │ haz… │ disabled │
└────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
This is the simplest way to use PM2. We will explore better ways in the next few sections.
Creating a Configuration File
Having a configuration file allows to better manage the apps we are deploying. You can add add multiple apps to a single configuration app, create multiple instances of ann app, manage the logs, and more.
To create a configuration file, type the following command in the terminal:
pm2 init simple
The above command will create a simple configuration file that contains the below content:
module.exports = {
apps : [{
name : "app1",
script : "./app.js"
}]
}
We need to change the script
value to server.js
since that is our filename, and we can also change the app name to server
:
module.exports = {
apps : [{
name : "server",
script : "./server.js"
}]
}
Now, to start the application, run the following command:
pm2 start ecosystem.config.js
You should get an output about the app you just created:
[PM2][WARN] Applications server not running, starting...
[PM2] App [server] launched (1 instances)
┌────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ server │ default │ N/A │ fork │ 608309 │ 0s │ 0 │ online │ 0% │ 31.4mb │ hazem │ disabled │
└────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
The output includes the app ID in PM2, the app name, PID, status, and even the CPU and memory usage.
Now that we have a configuration file for our app, we can start customizing it. To see the list of options, check out the link below: https://pm2.keymetrics.io/docs/usage/application-declaration/
We can configure the logging paths for normal logs and error logs, what to do if the app fails or needs a restarts, the deployment information such as the SSH host and user, and even if the number of instances of our app we want to have.
We will try out a couple of these configurations below.
Logging
We can configure the log file paths for error logs or for general logs by changing the configuration to:
module.exports = {
apps : [{
name : "server",
script : "./server.js",
error_file: "./logs/error-server-app.log",
out_file: "./logs/logs-server-app.log"
}]
}
We added two variables to our app configuration:
error_file
- this is the path of the file where all the error logs will be stored.out_file
- this is the path of the file where all the other logs will be stored.
To start seeing these logs, we first delete the current instances of the app, then create them from scratch:
pm2 delete ecosystem.config.js
pm2 start ecosystem.config.js
We should start seeing a folder called logs
in the app directory with two files: the error-server-app.log
and logs-server-app.log
.
The error file will be empty for now since there are no errors. But if we take a look at the logs-server-app.log
file, we will see the following message:
Accepting requests on port 8000
Cluster Mode
Cluster Mode in PM2, is a mode which allows the app to run on multiple instances on the different CPUs on a single system. Using load-balancing algorithms, the requests are divided on the different instances of the app. This is done with the help of the Cluster Module in Node JS.
To learn more about the Cluster Module, check out my article below: https://hazemhadi.com/articles/multi-cpu-node-js-cluster-module/
To get started with the Cluster Mode in PM2, change the configuration as follows:
module.exports = {
apps : [{
name : "server",
script : "./server.js",
error_file: "./logs/error-server-app.log",
out_file: "./logs/logs-server-app.log",
instances: "max",
exec_mode: "cluster"
}]
}
We added two more variables in the configuration:
instances
- this variables specifies how many instances you want of the application, it is a numerical value. But we also have the max option, which runs as many instances as there are CPUs on your device.exec_mode
- this tells PM2 to run in the cluster mode, which load-balances the requests coming to the application.
To create the different instances of the app, we first delete the current instances of the app, then create them from scratch:
pm2 delete ecosystem.config.js
pm2 start ecosystem.config.js
To see the list of apps you have running in PM2, run the following command:
pm2 ls
Now, you should see the list of instances of the app created by PM2 like so:
┌────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0 │ server │ default │ N/A │ cluster │ 531713 │ 6m │ 0 │ online │ 0% │ 50.0mb │ hazem │ disabled │
│ 1 │ server │ default │ N/A │ cluster │ 531723 │ 6m │ 0 │ online │ 0% │ 50.8mb │ hazem │ disabled │
│ 2 │ server │ default │ N/A │ cluster │ 531733 │ 6m │ 0 │ online │ 0% │ 50.7mb │ hazem │ disabled │
│ 3 │ server │ default │ N/A │ cluster │ 531743 │ 6m │ 0 │ online │ 0% │ 50.7mb │ hazem │ disabled │
└────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
The output might be slighly different for you. But the idea is seeing how there are multiple instances of the same application, running at the same time, and all managed by PM2.
Conclusion
In this tutorial, we went through what PM2 is and the installation, how to use it, and how to create and customize the configuration file.
We were able to set up our app in the configuration file, we added custom log paths, and we even added multiple instances of the same application to run on different CPUs.
Thank you for going through the tutorial, I hope you learned about PM2 and feel confident enough to use it in your own projects.