How to deploy a nodejs application on ubuntu 16.04 with git and pm2?

November 17, 2017

 - Tags: ,

Have you ever wondered how to setup a deployment process as smooth as the famous git push workflow of Heroku? You just push your code to a remote git repo, and your application is automatically deployed.

Let’s learn how to do this in less than 15 mins, with git, pm2 and nodejs.

Please note that this is only one of the the many way to do what we call continuous deployment. There are other options for continous deployment such as setting up a dedicated tool like Jenkins. But for most developers, the git push approach explained here will be enough. Let’s dive in!

Provision a VM

First, you will need to provision a VM with your favourite cloud provider, like AWS, Google Cloud Engine, or Digital Ocean. Alternatively, you can also use a tool like Vagrant if you just want to test this setup on your local computer. I will not cover this part. Once this is done, ssh into your server, and you will be ready to follow the rest of this tutorial.

Install nodejs, git and pm2

For our example, we will deploy a nodejs application, but the underlying concepts can be used with other programming languages as well. Please note that nodejs is also required for pm2.

sudo apt-get update
sudo apt-get install nodejs

pm2 is a process monitoring tool for nodejs. We will use it to monitor and run our application. When nodejs was installed, it also installed npm, i.e Node Package Manager. We will use npm to install pm2. Please note that we use the global flag -g, in order to be able to use pm2 from anywhere on the command line:

npm install -g pm2

The primary purpose of git is to be a version control system. But we can still use it for simple continuous deployments. Let’s install it

sudo apt-get install git

Create the directories for our project

We will create a root directory for our project (myproject), a directory for checking out the code of our application (myproject/site), and a directory for our git repo (myproject/site.git):

mkdir ~/myproject
mkdir ~/myproject/site
mkdir ~/myproject/site.git

Setup pm2

We first need to checkout the initial version of our repo. Navigate to the directory that is supposed to receive your code, i.e myproject/site, and checkout your code from your git repo:

cd ~/myproject/site

Assuming our the entry point of our nodejs application is a file called index.js, we will start our application with pm2:

pm2 start index.js --name myNodeApp

We specified the flag –name to be able to easily identify our process with pm2 tools. There is also a –watch flag that make pm2 reload the application when any source code file changes. Since we will manage ourselves the application reloading after each git push, we don’t need to use this option.

Now, if you run:

pm2 list

You should see your application, as well as some related statistics:

###############################################
# Name      # mode # status # cpu # memory    #
###############################################
# myNodeApp # fork # online # 5%  # 61.5 MB   #
###############################################

If you don’t see a status different than online, it means there is a problem with your application and need to debug it before using pm2.

pm2 will take care of restarting your application if it crashes or if is killed. However, the main process of pm2 itself was launch manually outside the supervision of the OS and will not persist in case or server crash or restart. In order to make sure your application will survive those events, we will make pm2 a supervised process under systemd, a supervision tool shipped with Ubuntu 16. Run this command to create a startup script for systemd:

 
pm2 startup systemd

You should see an output similar to this:

 
[PM2] Init System found: systemd
[PM2] You have to run this command as root. Execute the following command:
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u myuser --hp /home/myuser

Now you just need to run the generated command to start pm2 as a supervised process:

 
sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u myuser --hp /home/myuser

Setup our git repo and a post receive hook

We will step into our empty git directory, and initiate a git bare repo:

cd ~/myproject/site.git
git init --bare

A git bare repo has only the content of a .git directory, but no working tree.

Then, we will need to use to setup a git hook. A git hook is a piece of code that can be executed everytime a certain action is performed on the git repo. In our case, we will setup a post-receive hook that is executed every time someone pushes to the git repo.

You will see different templates for git hooks in the /hooks subdirectory:

ls ~/myproject/site.git
applypatch-msg.sample  pre-applypatch.sample      pre-rebase.sample
commit-msg.sample      pre-commit.sample          update.sample
post-receive.sample    prepare-commit-msg.sample
post-update.sample     pre-push.sample

Create a post-receive file:

touch post-receive

Then paste the following snippet in the post-receive file:

git --work-tree=~/myproject/site --git-dir=~/myproject/site.git checkout -f
cd ~/myproject/site
npm install
npm start

The first line tells git to checkout the HEAD of the repo to the ~/myproject/site directory. The checkout operation might result in the loss of some changes in the working directory, if it is not empty. In this case, git will ask us to first stash those changes before completing the checkout operation. In our case, we don’t care about this safety measure, so we tell git to ignore it by specifying the -f (force) flag.

git --work-tree=~/myproject/site --git-dir=~/myproject/site.git checkout -f

We then step into the working directory where the updated code is now checked out:

cd ~/myproject/site

It’s a best practise not to commit your dependencies in your git repo. But it means that when you check out your code in a working directory, you will miss all your dependencies. So we will install them with npm:

npm install

Now that our code is checked out and our dependencies are installed, we are ready to (re)start our application:

pm2 restart myNodeApp

After reviewing our post-receive hook, we need to make this file executable. Replace by your current user (type whoami if you don’t know your user).

sudo chmod +x <user> ~/myproject/site.git/hooks/post-receive

Finally, on your local computer, you need to add the remote git repo (i.e the bare repo you just setup setup) as a remote:

cd /path/to/your/project/
git remote add prod ssh://<user>@<ipaddress>/myproject/site.git

Check that everything worked

To check that everything work, you need to:

  • Make a small change to your local repo
  • commit it locally
  • push it to your server (push prod origin master)
  • On your server, run pm2 to check that your node process restarted and is still running

Congrats! You just setup a continuous deployment environment for your nodejs application, using git and pm2. Now, relax and enjoy the smoothness and the speed of your new workflow!

Leave a Reply