One reason for migrating to static site generation is that the site becomes more straightforward to keep in version control systems such as git. And I do like version control; I naturally ran git init before I ran jekyll new!

In my workflow, once I have made and committed changes locally, I push the changes remotely. But now I need to build and deploy the new version of the site. What if I could get that to happen automatically?

To do this, I set up my web server to accept git commands; this is a topic unto itself which I might write about some time.

I then create a bare repo:

  git init --bare blog.git

After setting permissions as appropriate, I can now push my git repo to the web server:

  git remote add origin https:[email protected]/git/blog.git
  git push -u origin master

I then modified an example git-hook I found on the Jekyll website to be more robust and meet my needs. There are a few things to take note of here:

  • I switched from using the git-receive hook as per Jekyll’s recommendation to post-receive. This is because the level of info git-receive, ahem, receives seems a bit overkill — post-update provides the names of the modified branches, and that’s all we need.
  • DEPLOY_REF_REGEX is a regex that matches git-refs, such as branches. The value in my script means that it’ll only deploy the website when changes are pushed to master. This means you are free to experiment on non-master branches. I haven’t gone so far as to deploy different branches different places!
  • GIT_REPO is the path to your site’s git repo. Ensure it can be read by the webserver.
  • PUBLIC_WWW is where the website needs to be deployed to be visible on the web. Ensure it is writable by the webserver.

To use this hook, create a file called post-update in your remote bare repo’s hooks directory. Ensure it is executable by your webserver!

  # Builds and deploys blog when new commits are pushed to the specified branches (default master).
  # Despite the Jekyll site recommending to use the post-receive hook, we do not
  # need the level of information that post-receive gets.  post-update receives
  # each ref that was updated as an argument, and so is sufficient for this
  # script's needs.
  # The script also adds the commit ID of the deployed commit to the site, so you are able
  # to tell exactly what is live by visiting /deployed_commit.
  # Originally taken from:



  for ref in "$@"; do
    if [[ $ref =~ $DEPLOY_REF_REGEX ]]; then

  if [ $DEPLOY ]; then
    TMP_GIT_CLONE=$(mktemp -d)
    git clone "$GIT_REPO" "$TMP_GIT_CLONE"
    echo $(GIT_DIR=.git git -C "$TMP_GIT_CLONE" rev-parse HEAD) > "$TMP_GIT_CLONE/deployed_commit"
    jekyll build -s "$TMP_GIT_CLONE" -d "$PUBLIC_WWW"
    rm -Rf "$TMP_GIT_CLONE"
    echo "Updated refs did not match; not deploying."


You’ll get output like the following:

  $ git push
  Counting objects: 4, done.
  Delta compression using up to 4 threads.
  Compressing objects: 100% (4/4), done.
  Writing objects: 100% (4/4), 3.15 KiB | 0 bytes/s, done.
  Total 4 (delta 2), reused 0 (delta 0)
  remote: Cloning into '/tmp/tmp.TkcxSnfVnb'...
  remote: done.
  remote: Configuration file: /tmp/tmp.TkcxSnfVnb/_config.yml
  remote:             Source: /tmp/tmp.TkcxSnfVnb
  remote:        Destination: /var/www/blog
  remote:       Generating...
  remote:                     done.
  remote:  Auto-regeneration: disabled. Use --watch to enable.
  To https:[email protected]/git/blog.git
     df05e56..ce42b80  master -> master

If the deploy succeeds, then website should be available on the internet and you are done! If the deploy fails, the push will be cancelled and you’ll need to investigate why that might be. I would first check the permissions on all of the directories mentioned above.

Finally, as a little bonus, the script creates a deployed_commit file which contains the commit SHA of the commit the build was based on; useful if something goes wrong or you just want to check what you last deployed. You can even check my site’s deployed commit!