Easy Jekyll Deployment with Grunt and Git

12th September 2014

I've recently re-built this site using Jekyll, for reasons I'll go into in another blog post. I wanted to make deploying changes really, really easy. In this post, I'm going to detail my approach.

Hosting

Jekyll plays nicely with Github Pages. However, I haven't taken advantage of it, for two reasons. The first is that I have a number of unsupported plugins, so I can't take advantage of their automatic Jekyll building process. That's not necessarily an impediment to using Github Pages, since there are workarounds. A bigger issue is that you currently cannot use HTTPS with a custom domain, which is suddenly really important as far as search rankings go, following Google's recent announcement.

One of the great things about using Jekyll is that because it's all static files, hosting requirements are nominal. So, I decided to keep my Digital Ocean droplet (disclaimer: that's an affiliate link). It's pretty low-spec, but if it comes to it, resizing a Droplet is a piece of cake.

So that's the hosting, now I'll go through the steps involved in my deployment process.

Overview of the Deployment Steps

Grunt

I'm using Grunt to perform a number of tasks:

  • Compiling some SASS files
  • Concatenating the CSS
  • Minifying all my CSS

I've configured a default task for these, so I simply need to run grunt. There are no Javascript-related tasks, such as linting or minifying, simply because there isn't any Javascript.

I've also added a task to optimize all my images, but haven't included it in my default Grunt configuration, simply because it takes a while - I just run it as and when I need to.

Jekyll

The next step is of course to run jekyll build. This regenerates the website in the _site directory. I've set up Git so that this directory is included in the repository. This is contrary to a lot of setups, which deliberately ignore the built version of the site - but since I'm building it locally, it's necessary to include it.

Git

The next step in my deployment process is to commit any changes to Git. Then, I'm pushing those changes to the master branch on Bitbucket, as well as the "live" site by using a Git remote. I'll go through that part of the process shortly.

The Deployment Process

The first thing I did was add my SSH key to the Droplet, so I wouldn't need to enter a password everytime 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 create a new directory on the remote server:

cd /var/www
mkdir example.com

Then I created a new, empty Git repository:

cd example.com
git init

I set the receive.denyCurrent Git setting to ignore for the repository - here's the complete file (.git/config) for demonstration:

[core]
 repositoryformatversion = 0
 filemode = true
 bare = false
 logallrefupdates = true
[receive]
 denyCurrentBranch = ignore

Next, I configured 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 created a post-receive hook on the web server:

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

Here are the contents of that file:

#!/bin/sh
cd ..
GIT_DIR='.git'
GIT_WORK_TREE=/var/www/example.com git checkout -f
umask 002 && git reset --hard

Essentially, this synchronises the remote server (i.e. my web host)'s copy of the Git repository which holds the site, whenever 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 deployment script. I created a file named simply deploy with the following contents:

#!/bin/bash
grunt
jekyll build
git add . -A
git commit -am "Updated website"
git push production master

This does the following:

  1. Runs my Grunt tasks (compiling SASS, concatenating and then minifying the CSS)
  2. Builds the site
  3. Adds any newly created files to Git. The -A flag (all) also ensures any deleted files are removed from Git (see this post for further explanation)
  4. Commits the changes to Git
  5. Pushes up to the remote server, where the Git hook takes care of updating the "live site"

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

./deploy

Extending for Multiple Environments

If you have multiple environments - for example production and staging - you can expand this script to ask which one to push to. Here's the modified script:

#!/bin/bash
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;;

    esac
    break

done

echo "Pushing to $env"

grunt
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 - deploying is now a piece of cake.