Show Buttons
Share On Facebook
Share On Twitter
Share On Google Plus
Share On Linkdin
Share On Reddit
Share On Stumbleupon
Contact us
Hide Buttons

Push to deploy a nodejs application using git hooks

Application deployment is a repetitive and boring task. And any task that is repetitive should be automated so that we can use our mental abilities to do better things than repeat ourselves. And deploying nodejs applications is no exception.

Only recently did I setup Ubuntu to dual boot with my windows machine as the primary operating system for working on personal projects. Since almost all of my projects are now based on nodejs, I decided to take a tiny leap forward towards Continuous Integration (CI) in my personal projects by setting up automated deployment using git push.

The concept of automated deployment is pretty simple. Assuming that you are working in git, you would have a number of branches that you work on in your development repository. The primary branch is always master. If you write code by the philosophy that every commit in master is gold standard, i.e. it is production ready, it means that whenever you commit to master, you also want that code to be deployed to your production environment at that very moment so that you customers can have the latest and greatest version of your code whenever its ready.

Ideally you will have a local repository in which you do all your development, a remote repository to which you push to when you want to share code with your team and a folder in the server that serves as your application. And these folders may or may not be on different machines.

To make things a little more fun, I also have a test branch which is silver standard, i.e. every commit made to that branch should be stable and can be integration tested.


What will we have done by the end of this tutorial?

Here’s what we want to achieve by the end of this tutorial: Whenever we push the master branch or the test branch to the remote, the latest commit in these respective branches should be auto deployed in their respective production folders.

For the sake of convenience, this is how I have setup my environment, all on the same ubuntu machine.

  • Local Repository: Git repository that has at least two branches “master” and “test”.
    • /home/ryan/projects/push2deploy
  • Remote: A bare git repository
    • /home/ryan/remotes/push2deploy
  • Remote: Two folders that run the live code – one for production and one for test
    • /home/ryan/sites/push2deploy/production
    • /home/ryan/sites/push2deploy/test

We will setup such that our production server will run on port 1234 and the test server will run on port 5678.

Also, we will be using pm2 as our application runner instead of just using a simple node app, or npm start script since it provides a really nice interface to monitor our application.

Lets say this is what my branching strategy looks like in my local repository.

branch structure


And this is what I want to do.

branch structure


Lets begin.

Create a bare repository from your actual remote repository or from your local repository itself.

cd /home/ryan/remotes
git clone --bare /home/ryan/projects/push2deploy push2deploy.git

In your local repository, add a new remote that points to this bare repository. Lets call this remote ‘deploy’ for convenience.

cd /home/ryan/projects/push2deploy
git remote add deploy /home/ryan/remotes/push2deploy.git

Create the directories for live code

mkdir -p /home/ryan/sites/push2deploy/production
mkdir -p /home/ryan/sites/push2deploy/test

With that out of the way, the next thing that we gotta do is create a script file within our hooks folder inside our bare repository and name it post-receive. Note that it does not have a .sh extension. This file will be executed by git whenever you push to the remote repository.

cd /home/ryan/remotes/push2deploy.git
touch hooks/post-receive
chmod +x hooks/post-receive

This is how things should look like at this stage.

directory structure

Since we will be using pm2, go ahead and install it using the command

npm install [email protected] -g

Or just follow their install instructions here

The last part of the setup process is to actually write the script. Here’s the script that I use for this example. I haven’t done shell scripting in my life, ever, so if there is something that could be improved in this script, do let me know.

#!/bin/sh
# 
## store the arguments given to the script
read prevCommitSHA latestCommitSHA fullbranchName

branch=${fullbranchName##*/}

WEBROOT="/home/ryan/sites"
PROJECT="push2deploy"
LOGFILE="$WEBROOT/$PROJECT/post-receive.log"
DEPLOYDIR=null
PORT=null

echo "log: $LOGFILE"

##  Record the fact that the push has been received
echo "Received Push Request at $( date +"%F %T" ) for #branch $branch"  >> $LOGFILE

echo "Checking deployment rules for project:$PROJECT, branch: $branch"

# Log the branch name
echo "---------------------------Deploy Start-------------------------------------" >> $LOGFILE

if [ $branch = "master" ] 
    then
    DEPLOYDIR="$WEBROOT/$PROJECT/production"
    PORT=1234
fi

if [ $branch = "test" ] 
    then
    DEPLOYDIR="$WEBROOT/$PROJECT/test"
    PORT=5678
fi

if [ $DEPLOYDIR = null ] 
    then
    echo "Received branch $branch, not deploying."
    exit
fi

pm2 stop "${PROJECT}_${branch}" >>null
echo "Stopped server at $( date +"%F %T" )" >> $LOGFILE

echo "Deploying to $DEPLOYDIR" >> $LOGFILE

echo " - Starting code checkout"  >> $LOGFILE
GIT_WORK_TREE="$DEPLOYDIR" git checkout -f "$branch"
echo " - Finished code checkout"  >> $LOGFILE

echo " - Starting npm install" >> $LOGFILE
cd "$DEPLOYDIR"
rm -rf node_modules
npm install  >> $LOGFILE
echo " - Finished npm install"  >> $LOGFILE

echo "Restarting server using pm2 at $( date +"%F %T" )" >> $LOGFILE

PORT="$PORT" pm2 start app.js --name "${PROJECT}_${branch}" >> $LOGFILE
echo "Restart completed at $( date +"%F %T" )"  >> $LOGFILE
cd - 1>>/dev/null
echo "---------------------------Deploy Complete---------------------------------" >> $LOGFILE

echo "Done. Run 'pm2 ls' on the server to see the process status."

The script performs a very simple task.
I first read the arguments to the script. The third argument is what matters to me aka the branch name.

read prevCommitSHA latestCommitSHA fullbranchName

The script receives the full branch name as the argument – refs/heads/<branchName>. I am only interested in the last part so I do a little string manipulation to extract that into the ‘branch’ variable.

branch=${fullbranchName##*/}

Later, depending upon the branch, I set the deployment directory variable and the port number.

if [ $branch = "master" ] 
    then
    DEPLOYDIR="$WEBROOT/$PROJECT/production"
    PORT=1234
fi

if [ $branch = "test" ] 
    then
    DEPLOYDIR="$WEBROOT/$PROJECT/test"
    PORT=5678
fi

if [ $DEPLOYDIR = null ] 
    then
    echo "Received branch $branch, not deploying."
    exit
fi

Then I set a variable GIT_WORK_TREE to the deploy directory and checkout the code from the appropriate branch into this directory.

GIT_WORK_TREE="$DEPLOYDIR" git checkout -f "$branch"

Then, I delete the node_modules folder and reinstall the modules just in case there is a change.

rm -rf node_modules
npm install  >> $LOGFILE

Then I start my server using pm2 and give it a name with the pattern projectName_branchName while passing in the port number as the environment variable.

PORT="$PORT" pm2 start app.js --name "${PROJECT}_${branch}" >> $LOGFILE

You can check the status of any running application managed by pm2 using the command pm2 ls.

The only thing left to do is make some change and push it to the remote. So, go ahead and make some change to a file in a test branch and save and commit. Then simply do

git push deploy test

And that will push your chance to the remote named ‘deploy’ as well as run your application code from the test folder. A push to the master will work in the exact same way after you have made any changes.

git push deploy master

Visit your domain at the correct ports and you are now able to to view different versions of your application corresponding to your production and test branch instantly whenever you do a git push.

Ryan Sukale

Ryan is just a regular guy next door trying to manage his life and finances.

  • Corey Cauble

    how would this look if you were deploying to another machine in another location? this seems like it would work for a single machine nicely.

  • peter

    omg, this is something that i need !!

  • tutorialhorizon

    Yes feel free to do so!