/ HotM

Using Git and PM2 to Manage a Node App

   I worked on the House's infrastructure, incorporating Git and PM2. If you manage your own website, and do not use Git to help you, I recommend reading Using Git to manage a web site by Abhijit Menon-Sen. I do but did not until after I read that article.

TL:DR? read Abhijit's article instead.


Chromebook (dev machine)

   I primarily use a chromebook by Acer as my personal dev machine and web appliance. This was the cheapest linux based laptop with fully integrated hardware I could get my hands on. I also like the Chromebook's minimalism, and built-in full screen mode.
   Out of the box, a Chromebook is not a dev machine, but a netbook. To make it a dev machine I put the thing in developer mode, and installed crouton, a script which allows me to run Ubuntu in a chroot. Follow the instructions at those links if you wanna do this too. Doing all that will take awhile, but this article will probably still be here in the future if you want to resume later.
   Once using the shell inside the chroot, setup is similar to the actual server as shown below, because both use the same release of Ubuntu. The main difference is that I am not using PM2 on the Chromebook. I restart my local web apps as needed from the command line with npm start. Maybe it would be nicer to use a file watcher with PM2 to automate restarts, but this has less moving parts.

Rented VM (server)

   I set up Ubuntu on a virtual machine server with Vultr which operates like a computer, setup and managed by myself. I did not want to deal with all those other layers of cloud services. Not included in my instructions here is getting yourself a VM and setting up ssh access with a user account other than root. Step 0 is doing all that and ssh'ing yourself in: ssh your_user_name@your_website_domain.

Software Installation at the Command Line:
Ubuntu: preinstalled but let's update just in case it's been awhile
sudo apt-get update
sudo apt-get install nginx
Node.js and NPM
curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash
sudo apt-get install -y nodejs
sudo npm install -g pm2
sudo apt-get install git
Exit the server after you are done

   You will notice that I am using version 6.x of node which is not the latest version. I am using version 6 because that is what is recommended for Ghost, which is the software I use for this blog. You may want to use NVM to manage multiple versions of node. On your dev machine there is some merit to that as it affords you the opportunity to use each version. You could do that on your server too, but it complicates things so I am not, and I can not gaurantee my instructions will work for you if you do because NVM stores the node binaries in different places than normal, and because each version stores its own global modules in a different directory. If you want to play with different versions of node, my recommendation is to use NVM locally on your dev machine, but to pick one version of node for your server and only install that there.


Local Repository

   My local repository is the same directory from which nginx serves locally: /var/www/arbitrarily_named_directory/.

-rw-rw-r--.   1 chronos chronos   798 Jun 23 06:30 .eslintrc.json
drwxrwxr-x.   8 chronos chronos  4096 Jun 23 05:57 .git
-rw-rw-r--.   1 chronos chronos    76 Jun 20 09:45 .gitignore
-rwxrwxr-x.   1 chronos chronos  1759 Jun 23 07:58 index.js
drwxrwxr-x. 249 chronos chronos 12288 Jun 23 08:00 node_modules
-rw-rw-r--.   1 chronos chronos   772 Jun 20 09:20 package.json
drwxrwxr-x.  10 chronos chronos  4096 Jun 12 12:39 public_html
-rw-rw-r--.   1 chronos chronos  1069 Jun 20 10:04 README.md
drwxrwxr-x.   2 chronos chronos  4096 Jun 20 07:03 src
drwxrwxr-x.   3 chronos chronos  4096 Jun 20 07:03 views

   Two things worth noting. First, this is an NPM package, hence the package.json. Second, it is tracked by Git as indicated by the .git directory. The instructions below show you how to make this happen.

Setting up:
Go to the directory from where you serve your web apps
cd /var/www
Make a directory for your new web app
mkdir your_website_name
Enter the directory
cd your_website_name
Initialize the directory for git
git init
Initialize it for npm, pressing 'enter' for all the questions
npm init
Add a start script to package.json
nano package.json
replace the bogus test script with

"start": "npm install && ./index.js"

   After following those instructions, you will have a .git directory and a package.json file to respond to your git and npm commands in that directory. Note however that this is obviously not a working website. There are no html or javascript files in this directory. Now would be the time to copy your web app's files into this directory if you have them. Edit the start script in package.json to execute the appropriate file in your web app.
   If you do not have a node web app, you will have to get the files for one in order to continue to follow along. I have a bare bones express app which serves static html that you may use. Clone it to your home directory, and then copy the files to this example web app we are working on.

Cloning the example website:
Go to your home directory
cd ~
Make a place to store code experiments and go there
mkdir code
cd code
Clone my code from github
git clone git@github.com:aaronmfparr/magus-static-express.git
Copy the files
cd magus-static-express
cp -R * /var/www/your_website_name

   Before pushing all of this to your server, you need a repository to push it to, the master repository. You just initialized your local repository, but you also want an upstream master located on the machine that serves your webpages. That is why this is cool. Instead of using github, you will create and serve your own repo, and that repo will in turn update your website's files. There are still a few more steps required to put this all together.
   Your next step is to ssh your virtual ass on over to your server:

ssh your_user_name@your_website_domain


   You will land in your home directory. That is a fine place to make your master repo if only you are accessing it, which is my situation. If you are setting up a project for multiple people, you may want to put your master repo in /opt, and you will want to get to work on user accounts, permissions, and security.

Make the master repo:
Moving to the home directory (if not already there)
cd ~
Make the master repo
git init --bare your_website_name.git
Create the post-receive hook
nano your_website_name.git/hooks/post-receive

while read oldrev newrev refname
branch=$(git rev-parse --symbolic --abrev-ref $refname)
if [[ $branch = *"/master" ]]; then
# migrate files from the master branch to the website
GIT_WORK_TREE=/var/www/your_website_name git checkout -f master
# restart the node app with pm2
pm2 restart /home/your_user_name/your_website_name-pm2.config.js --update-env

Save post-receive and exit nano
ctrl-o and ctrl-x
Make the hook executable
sudo chmod +x your_website_name.git/hooks/post-receive
Make the website directory
mkdir /var/www/your_website_name

   What did we just do? First, we created a master repo, but it looks different than the local. The argument --bare made the difference. In git speak, this is a bare repository. I call it the master repo because I don't work inside it, and it is upstream of everywhere I do work. I push to it and pull from it. Second, the post-receive hook is the script which responds to every push to this repo. This script checks-out the latest on the master branch into the web directory served up for nginx. Then it restarts the website using pm2. But we have not set up pm2 yet and won't until after we have pushed our node app from our local to the master. Let's do that now.

Exit your server


   Locally all you need to do is commit the files in your local repository and push them to the server.

Commit and push your app to the master repo
Return to the local repo for your web app
cd /var/www/your_website_name
Connect the local to your upstream repo
git remote add your_user_name@your_website_domain:/home/your_user_name/your_website_name.git
Add and commit your local files
git add *
git commit -m 'Your first commit message here.'
git push --set-upstream origin master

   You will probably see an error, but that is ok. We have to set up PM2 to start and restart your web app. Your files however should now be both in the master repo, and in the directory they are served from. Let's go take a look.

ssh your_user_name@your_website_domain


   Verify that your new work migrated to the server.

cd /var/www/your_website_name
ls -l
You should see something like:

-rwxrwxr-x   1 chronos chronos   1632 Jun 23 05:44 index.js
-rw-rw-r--   1 chronos chronos    772 Jun 20 17:05 package.json
drwxrwxr-x  10 chronos chronos   4096 Jun 20 16:22 public_html
-rw-rw-r--   1 chronos chronos   1069 Jun 20 17:05 README.md
drwxrwxr-x   2 chronos chronos   4096 Jun 23 05:44 src

   If you do not, you made a mistake (or I gave bad instructions) somewhere along the way. Play around with the post-receive hook. Edit the files in your local repo and push again. Try something else. Eventually you will get it working, and you will learn much more along the way.
   We now only have a few more things to do before we are finished.


   Two things remain. First, NGINX should be set up to proxy to your node app. Second, PM2 needs to be set up as the daemon to start, stop, and restart your web app. I am just going over this for your server, but these instructions could also apply to your dev machine with a caveat: a chromebook using crouton handles services like this a little differently.


   If your NGINX server is not yet configured and you do not know how to do that, please read this HOW TO published over at Digital Ocean. The NGINX config file you create however will look something like this:

server {
  listen 80 default_server;
  listen [::]:80 default_server;

  root /var/www/your_website_name;

  server_name your_website_domain;

  location / {
    proxy_pass_request_headers	on;
    proxy_pass_request_body     on;
    proxy_pass        ;

   Note the port number at the end of the url for proxy_pass: 3000. Ensure that that number matches the port number at which your web app is listening. Also, whenever you edit your NGINX config files, you need to restart NGINX. That Digital Ocean article covers this stuff.


   With NGINX serving as it should, you now just need to setup PM2 to keep your web app up and running. You'll be making an ecosystem file for PM2, and training PM2 to use it. For more documentation about PM2, see the PM2 documentation.

Setting up PM2
return to home
cd ~
make an ecosystem file
pm2 ecosystem
Take a look at it
nano ecosystem.config.js
Delete it
rm ecosystem.config.js
Make your own in nano from scratch
nano your_website_name-pm2.config.js
like this

module.exports = {
  apps : [{
    name    : "your_website_name",
    cwd     : "/var/www/your_website_name",
    script  : "npm",
    args    : "start",
    watch   : false

Save the config and Exit nano
ctrl-o and ctrl-x
Start your web app
pm2 start your_website_name-p2.config.js
Checkout the process
pm2 list
Try restarting it manually
pm2 restart your_website_name
Connect PM2 with your server's startup daemons
pm2 startup
Follow the instructions you are given then...
Save the process
pm2 save


   You are done. Now when you push changes to the master branch, your git repo will send them on to the live web app, and then PM2 will restart your web app. Note that the npm start script we are using first executes npm install in case you have added node modules before it goes on to relaunch the app. For typical websites this is fine. If your web app becomes a serious undertaking and requires a significant build process, you will want to change this.
   If you are playing with the little express app I shared, you will notice that it only returns a 404 error page. You can fix this. Go to your local repository. Create a "hello,world!" index.html or whatever you want in the public_html directory. Commit the file and push it to your master repo. Check your website.
   Yes, using node to serve static web files alone is a waste of resources. You can use express to do a lot more.

Using Git and PM2 to Manage a Node App
Share this