Easy Jekyll Deployment with Grunt and Git
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:
- Runs my Grunt tasks (compiling SASS, concatenating and then minifying the CSS)
- Builds the site
- 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)
- Commits the changes to Git
- 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.