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.
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
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
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
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>
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
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:
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.