Martin Peck
Martin Peck I write code, and manage people who write code.

Building and Deploying Jekyll Sites using Docker - Part Two

Building and Deploying Jekyll Sites using Docker - Part Two

In Part 1 of this writeup I described how I’m using Docker to simplily my [Jekyll] static site builds on my local machine. In this, Part 2, I’m going to show how I’m using the exact same approach in my CI/CD pipeline using Azure DevOps and [Travis-CI][travis].

Using Jekyll and Docker within an Azure DevOps Pipelne

Building a Jekyll static site using Azure DevOps is really very simple. In fact, most of the set up is done for you!

Before you get started, make sure you already have a Azure DevOps organisation and project set up. If this if your first time, you should probably take a look at the Azure DevOps documentation and some of the getting started docs. After you’ve done that, create your Azure Pipeline, and point Azure DevOps at the location of your code.

First, Select a Pipeline Template

Azure DevOps offers a Jekyll site template pipeline

When you create your first Azure build Pipeline, Azure DevOps will suggest some Pipeline templates based upon the contents of your git repository. At this point, you should be offered “Jekyll site” as a template. As you can see, the description for this template is exactly what we want…building Jekyll using a docker container image.

Review the Pipeline YAML

Azure DevOps offers a Jekyll site template pipeline

Azure DevOps build pipelines are defined as .yaml files. This file will get committed to your source repository as azure-pipelines.yaml. The template is actually pretty good. As you can see, it’s using jekyll/builder:latest container image to run Jekyll and build your site. The command it runs is jekyll build --future which will build posts that are dated in the future, allowing this CI pipeline to check that your future dated posts aren’t broken.

The only needed to make a few changes to my azure-piplelines.yaml file to make it work for my GitHub Pages blog:

  • I changed the trigger from master to gh-pages so that builds were triggered on the right branch
  • I added another task to my build, to take the output of Jekyll build (i.e. the _site folder that Jekyll generates) and to publish it from my build as an Artifact

Here is my full azure-pipelines.yaml:

# Jekyll site
# Package your Jekyll site using the jekyll/builder Docker container image.
# Add steps that build, test, save build artifacts, deploy, and more:

- gh-pages

  vmImage: 'Ubuntu-16.04'

- task: Docker@0
  displayName: 'Run Jekyll'
    containerRegistryType: 'Container Registry'
    action: 'Run an image'
    imageName: 'jekyll/builder:latest'
    volumes: |
    containerCommand: 'jekyll build --future'
    detached: false
- task: PublishBuildArtifacts@1
    artifactName: 'drop'
    pathtoPublish: '$(build.binariesDirectory)'
    publishLocation: 'Container'

Run your First Pipeline Build

Azure DevOps build pipeline output

After you’ve reviewed/edited your .yaml file, Azure DevOps will ask you to commit the file and will then run a build.

And that’s it!

Now, every time I push changes to my gh-pages branch this Pipeline build will trigger, it will pull the source from GitHub, use the Jekyll container image to build my site, and will then take the generated site and publish it as a build artifact.

What Next?

If you simply want to run a CI build to confirm that your site builds, then you’re done. In my case, I publish the _site directory so that I can then use it within an Azure DevOps Release Pipeline

My Release pipeline is incredibly basic:

Azure DevOps Release Pipeline

Azure DevOps Release Pipeline Tasks

The end result is that the static site, built by Jekyll, is then published to the internet, and I can then review the output of the build. In my case, the build output ends up at:

Using Jekyll and Docker within a Travis-CI Build

Until recently, I was using [Travis-CI][travis] to perform CI builds of my blog. There are plenyy of examples of how to build Jekyll sites on Travis-CI, including the “offical” instructions on the website. However, almost all of these examples use the native Ruby support that Travis has. I wanted to see how easily I could get Travis-CI to use the same docker images that I was using for my local build, so that my local build and my CI build would be as similar to each other as posibble.

I won’t to into massive detail here, but the end result was that I got my build to work, but it wasn’t nearly as simple as it was on Azure DevOps, and I had to work around a permissions issue.

My final .travis.yaml looks like this:

language: python

  - docker

script: docker run --rm -it --volume="$PWD:/srv/jekyll" jekyll/jekyll:3.8 /bin/bash -c "chmod a+wx . && jekyll build"

# branch whitelist, only for GitHub Pages
  - gh-pages     # test the gh-pages branch
  - /pages-(.*)/ # test every branch which starts with "pages-"

You can refer to the official Travis-CI docs for Docker builds, but the highlights are:

  • I use the python language for this build purely to show that I’m not making use of any ruby services supplied by Travis-CI
  • I’ve added docker as a service. This allows you to use docker commands in your script
  • My scrip runs docker
  • I’ve had to work around a permissions issue

You’ll notice that I’m not running jekyll build directly, but instead I’m running a bash command that first uses the chmod command to change the permissions on the current directory (inside the container) before running the jekyll build. Without this, the container didn’t have permissions to write content to the _site folder.

If found several referneces to this issue, with no good conclusion. For me, this chmod hack worked fine, but it’s what caused me to take a look at the Azure DevOps pipeline, where I found the docs and support for docker within CI builds much more comprehensive.

In Summary…

I’m pretty happy with my Azure DevOps CI pipeline, and the Release pipeline that deploys my site. It was pretty easy to set up, and I’m happy that the way it builds the Jekyll site is pretty much the same as the way I built it locally.

I was actually quite surprised how difficult it was to do the same thing with Travic CI. I guess that the simple path for Travis CI builds is to use the Ruby language services they offer.

If you want to take a closer look at the builds I have set up then here are the links you’ll need:

The Azure DevOps Builds:

The azure-pipelines.yaml defintion of the builds:

The Azure DevOps Releases:

The Travis Build (that I’ll be switching off soon):