Easy Jekyll Deployment with Grunt and Git

12th September 2014 | Tags:

I’ve recently re-​built this site using Jekyll, for rea­sons I’ll go into in another blog post. I wanted to make deploy­ing changes really, really easy. In this post, I’m going to detail my approach.


Jekyll plays nicely with Github Pages. How­ever, I haven’t taken advan­tage of it, for two rea­sons. The first is that I have a num­ber of unsup­ported plu­g­ins, so I can’t take advan­tage of their auto­matic Jekyll build­ing process. That’s not nec­es­sar­ily an imped­i­ment to using Github Pages, since there are workarounds. A big­ger issue is that you cur­rently can­not use HTTPS with a cus­tom domain, which is sud­denly really impor­tant as far as search rank­ings go, fol­low­ing Google’s recent announce­ment.

One of the great things about using Jekyll is that because it’s all sta­tic files, host­ing require­ments are nom­i­nal. So, I decided to keep my Dig­i­tal Ocean droplet (dis­claimer: that’s an affil­i­ate link). It’s pretty low-​spec, but if it comes to it, resiz­ing a Droplet is a piece of cake.

So that’s the host­ing, now I’ll go through the steps involved in my deploy­ment process.

Overview of the Deploy­ment Steps


I’m using Grunt to per­form a num­ber of tasks:

  • Com­pil­ing some SASS files
  • Con­cate­nat­ing the CSS
  • Mini­fy­ing all my CSS

I’ve con­fig­ured a default task for these, so I sim­ply need to run grunt. There are no Javascript-​related tasks, such as lint­ing or mini­fy­ing, sim­ply because there isn’t any Javascript.

I’ve also added a task to opti­mize all my images, but haven’t included it in my default Grunt con­fig­u­ra­tion, sim­ply because it takes a while — I just run it as and when I need to.


The next step is of course to run jekyll build. This regen­er­ates the web­site in the _site direc­tory. I’ve set up Git so that this direc­tory is included in the repos­i­tory. This is con­trary to a lot of setups, which delib­er­ately ignore the built ver­sion of the site — but since I’m build­ing it locally, it’s nec­es­sary to include it.


The next step in my deploy­ment process is to com­mit any changes to Git. Then, I’m push­ing those changes to the mas­ter branch on Bit­bucket, as well as the “live” site by using a Git remote. I’ll go through that part of the process shortly.

The Deploy­ment Process

The first thing I did was add my SSH key to the Droplet, so I wouldn’t need to enter a pass­word every­time I deploy. This is really easy:

cd ~
cat .ssh/id_rsa.pub | ssh me@example.com 'cat >> .ssh/authorized_keys'

The next step was to cre­ate a new direc­tory on the remote server:

cd /var/www
mkdir example.com

Then I cre­ated a new, empty Git repository:

cd example.com
git init

I set the receive.denyCurrent Git set­ting to ignore for the repos­i­tory — here’s the com­plete file (.git/config) for demonstration:

 repositoryformatversion = 0
 filemode = true
 bare = false
 logallrefupdates = true
 denyCurrentBranch = ignore

Next, I con­fig­ured Apache to point to /var/www/example.com/_site.

Then I added a remote to my local Git repository:

git remote add production ssh://me@example.com/var/www/example.com

Next, I cre­ated a post-​receive hook on the web server:

nano /var/www/example.com/.git/hooks/post-receive

Here are the con­tents of that file:

cd ..
GIT_WORK_TREE=/var/www/example.com git checkout -f
umask 002 && git reset --hard

Essen­tially, this syn­chro­nises the remote server (i.e. my web host)‘s copy of the Git repos­i­tory which holds the site, when­ever I do a git push.

Note that this file needs to be made executable:

chmod +x /var/www/example.com/.git/hooks/post-receive

Next, my local deploy­ment script. I cre­ated a file named sim­ply deploy with the fol­low­ing contents:

jekyll build
git add . -A
git commit -am "Updated website"
git push production master

This does the following:

  1. Runs my Grunt tasks (com­pil­ing SASS, con­cate­nat­ing and then mini­fy­ing the CSS)
  2. Builds the site
  3. Adds any newly cre­ated files to Git. The –A flag (all) also ensures any deleted files are removed from Git (see this post for fur­ther explanation)
  4. Com­mits the changes to Git
  5. Pushes up to the remote server, where the Git hook takes care of updat­ing the “live site”

With all this in place, all I need to do to deploy my site is this:


Extend­ing for Mul­ti­ple Environments

If you have mul­ti­ple envi­ron­ments — for exam­ple pro­duc­tion and stag­ing — you can expand this script to ask which one to push to. Here’s the mod­i­fied script:

prompt="Pick an option: "
options=("Production" "Staging")

PS3="$prompt "
select opt in "${options[@]}" "Quit"; do 

    case "$REPLY" in

    1 ) env="production";;
    2 ) env="staging";;

    $(( ${#options[@]}+1 )) ) echo "Exiting."; exit;;
    *) echo "Invalid option. Try another one.";continue;;



echo "Pushing to $env"

jekyll build
git add . -A
git commit -am "Updated website"
git push $env master

Now when you run it, you get the following:

> ./deploy
> 1) Production
> 2) Staging
> 3) Quit
> Pick an option:  1
> Pushing to production

That’s it — deploy­ing is now a piece of cake.


No comments yet.

Links and images are allowed, but please note that rel="nofollow" will be automactically appended to any links.