How to use pm2 to serve a Node.js app
An introduction on how to manage Node processes on Linux and auto-restart them through GitHub webhooks
pm2 is a super useful process management tool for Linux.
I’ve used it for several projects of mine, and here I want to tell you how to use it too!
In particular I’m going to use it to run a Node.js app on a DigitalOcean VPS, and I’ll set it up so that whenever we push an update to the app’s GitHub repository, on the server pm2 will be pinged, and the app is going to be updated from GitHub and restarted.
Sounds cool? Let’s go!
First, sign up to DigitalOcean and follow my tutorial to create an VPS on DigitalOcean.
Important: use the
NodeJS
image on DigitalOcean, which is already set up withpm2
andnode
, and has anodejs
user in addition toroot
.
Once you’re up and running we can start.
Make sure you ssh as the nodejs
user. When you’re logged in as root, you can just run su nodejs
to use that user.
The sample Node.js app you have running in the VPS is in the folder /var/www/html/
, and it’s composed by the hello.js
file.
The deployment/running of the application is already managed through the pm2 program, a daemon process manager.
You can use pm2 list
to see all the applications running now:
If you do any change to the application now, the changes won’t be applied until you restart the application running:
pm2 restart hello
You can stop the application running
pm2 stop hello
and this will give you an error in the browser if you try to reload the page:
You can run:
pm2 start hello
to bring the app up again.
The nice thing about pm2 is that it will take care of running the app(s) again when the system is restarted.
Now that you’ve seen how the built-in hello world works, let’s deploy another application.
Let’s stop the current sample app:
pm2 stop hello
and create a test
folder in /var/www/html
:
mkdir test
go into that
cd test
and run
npm init -y
then install Express:
npm install express
Now run nano app.js
and add this code:
const express = require("express")
const app = express()
app.get("/", (*req*, *res*) => res.send("Hello World!"))
app.listen(3000, () => console.log("Server ready"))
Run
pm2 start app.js --name app
and you should see the app running!
We used port 3000, which is what the hello
app used, so we did not have to configure anything else.
Otherwise we’d need to modify the /etc/nginx/sites-available/default
and add a new URL to map to our app.
Now that we deployed a sample app, let’s see how to deploy an app from GitHub.
First thing, let’s stop the app we just built in the previous lesson:
pm2 stop app
and let’s make a sample GitHub app. It’s not going to be complex, it’s very similar to the app we built in the previous lesson, but it will say Hello World, deployed from GitHub!
instead of just Hello World!
On your local create a folder, and inside it run
npm init -y
then install Express:
npm install express
Now run nano app.js
and add this code:
const express = require("express")
const app = express()
app.get("/", (*req*, *res*) => res.send("Hello World, deployed from GitHub!"))
app.listen(3000, () => console.log("Server ready"))
Now it’s time to push the app to GitHub. I use GitHub Desktop, the official GitHub app, so I drag and drop the folder into the app, and it lets me create a new Git repo, and a GitHub repository.
I initialized the repo with the standard Node.js .gitignore
file, which ignored the node_modules
folder. We’ll see how to initialize the npm modules during deployment.
Now we have to create a SSH key on the DigitalOcean machine, and add it to GitHub. Otherwise we will not have access to the private repository we just created.
Double-check the email address you used to connect to GitHub, because it’s important. You can find it in the GitHub settings, in the Email panel. Then run:
ssh-keygen -t rsa -b 4096 -C "[email protected]"
Now open the “SSH and GPG keys” panel on GitHub:
and click “New SSH key”. Enter “DigitalOcean” as the title, so you know what this key is used for.
Now type
less /home/nodejs/.ssh/id_rsa.pub
on your DigitalOcean droplet to get the public key.
Copy that to your settings, and save. You should see it in the SSH keys list.
Now we should be able to clone the private repository on the server.
Go in the /var/www/html
folder, and run
git clone [email protected]:<yourname>/<reponame>.git
In my case it was:
git clone [email protected]:flaviocopes/testdo.git
and the app was successfully downloaded:
Now type cd testdo
and run npm install
.
The app is now ready to run, so type
pm2 stop test
to stop the previous app, and
pm2 start app.js --name testdo
to start the new one:
Install the pm2 module called pm2-githook
https://github.com/vmarchaud/pm2-githook using this command:
pm2 install pm2-githook
To configure the app, we need to use this command, which requires correct escaping of double quotes:
pm2 set pm2-githook:apps "{\"testdo\":{\"secret\":\"test123\",\"prehook\":\"npm install --production\",\"posthook\":\"echo done\"}}"
In this example I used the secret
value test123
, but change it to something less easy to guess.
By default this module listens on port 8888 for Webhooks coming from GitHub.
This port is now closed, open it using
sudo ufw allow 8888
Now open the repository settings on GitHub, and in the Webhooks section click Add Webhook.
Paste the URL of your domain, with port 8888.
In this example I used the secret
value test123
.
Now if you try doing a change to your code, it should automatically be deployed!
→ I wrote 17 books to help you become a better developer, download them all at $0 cost by joining my newsletter
→ JOIN MY CODING BOOTCAMP, an amazing cohort course that will be a huge step up in your coding career - covering React, Next.js - next edition February 2025