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

Appli­ca­tion deploy­ment is a repet­i­tive and bor­ing task. And any task that is repet­i­tive should be auto­mated so that we can use our men­tal abil­i­ties to do bet­ter things than repeat our­selves. And deploy­ing nodejs appli­ca­tions is no exception.

Only recently did I setup Ubuntu to dual boot with my win­dows machine as the pri­mary oper­at­ing sys­tem for work­ing on per­sonal projects. Since almost all of my projects are now based on nodejs, I decided to take a tiny leap for­ward towards Con­tin­u­ous Inte­gra­tion (CI) in my per­sonal projects by set­ting up auto­mated deploy­ment using git push.

The con­cept of auto­mated deploy­ment is pretty sim­ple. Assum­ing that you are work­ing in git, you would have a num­ber of branches that you work on in your devel­op­ment repos­i­tory. The pri­mary branch is always mas­ter. If you write code by the phi­los­o­phy that every com­mit in mas­ter is gold stan­dard, i.e. it is pro­duc­tion ready, it means that when­ever you com­mit to mas­ter, you also want that code to be deployed to your pro­duc­tion envi­ron­ment at that very moment so that you cus­tomers can have the lat­est and great­est ver­sion of your code when­ever its ready.

Ide­ally you will have a local repos­i­tory in which you do all your devel­op­ment, a remote repos­i­tory to which you push to when you want to share code with your team and a folder in the server that serves as your appli­ca­tion. And these fold­ers may or may not be on dif­fer­ent machines.

To make things a lit­tle more fun, I also have a test branch which is sil­ver stan­dard, i.e. every com­mit made to that branch should be sta­ble and can be inte­gra­tion tested.


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

Here’s what we want to achieve by the end of this tuto­r­ial: When­ever we push the mas­ter branch or the test branch to the remote, the lat­est com­mit in these respec­tive branches should be auto deployed in their respec­tive pro­duc­tion folders.

For the sake of con­ve­nience, this is how I have setup my envi­ron­ment, all on the same ubuntu machine.

  • Local Repos­i­tory: Git repos­i­tory that has at least two branches “mas­ter” and “test”.
    • /home/ryan/projects/push2deploy
  • Remote: A bare git repository
    • /home/ryan/remotes/push2deploy
  • Remote: Two fold­ers that run the live code — one for pro­duc­tion and one for test
    • /home/ryan/sites/push2deploy/production
    • /home/ryan/sites/push2deploy/test

We will setup such that our pro­duc­tion server will run on port 1234 and the test server will run on port 5678.

Also, we will be using pm2 as our appli­ca­tion run­ner instead of just using a sim­ple node app, or npm start script since it pro­vides a really nice inter­face to mon­i­tor our application.

Lets say this is what my branch­ing strat­egy looks like in my local repository.

branch structure


And this is what I want to do.

branch structure


Lets begin.

Cre­ate a bare repos­i­tory from your actual remote repos­i­tory or from your local repos­i­tory itself.

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

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

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

Cre­ate the direc­to­ries 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 cre­ate a script file within our hooks folder inside our bare repos­i­tory and name it post-receive. Note that it does not have a .sh exten­sion. This file will be exe­cuted by git when­ever 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 fol­low their install instruc­tions here

The last part of the setup process is to actu­ally write the script. Here’s the script that I use for this exam­ple. I haven’t done shell script­ing in my life, ever, so if there is some­thing 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 per­forms a very sim­ple task.
I first read the argu­ments to the script. The third argu­ment is what mat­ters to me aka the branch name.

read prevCommitSHA latestCommitSHA fullbranchName

The script receives the full branch name as the argu­ment — refs/heads/<branch­Name>. I am only inter­ested in the last part so I do a lit­tle string manip­u­la­tion to extract that into the ‘branch’ variable.

branch=${fullbranchName##*/}

Later, depend­ing upon the branch, I set the deploy­ment direc­tory vari­able 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 vari­able GIT_WORK_TREE to the deploy direc­tory and check­out the code from the appro­pri­ate branch into this directory.

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

Then, I delete the node_modules folder and rein­stall the mod­ules 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 pat­tern projectName_branchName while pass­ing in the port num­ber as the envi­ron­ment variable.

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

You can check the sta­tus of any run­ning appli­ca­tion man­aged by pm2 using the com­mand 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 com­mit. Then sim­ply do

git push deploy test

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

git push deploy master

Visit your domain at the cor­rect ports and you are now able to to view dif­fer­ent ver­sions of your appli­ca­tion cor­re­spond­ing to your pro­duc­tion and test branch instantly when­ever you do a git push.

Ryan Sukale

Ryan is a UX engineer living in San Francisco, California.

  • Corey Cauble

    how would this look if you were deploy­ing to another machine in another loca­tion? this seems like it would work for a sin­gle machine nicely.

  • peter

    omg, this is some­thing that i need !!