Building and Testing a Jekyll website with Docker on Windows

I’ve recently talked about [how I’ve changed my website to be created with Jekyll][migrate-to-jekyll]. One of the big reasons was to simplify how the site is built and run.

Well, if it’s so simple now, then I should be able to modify it on the go within Windows, right? I’ve recently been using an openSUSE VM to modify the site, but figured I could do one better and operate (mostly) natively. This article describes my solution.

It is assumed that you already have a Jekyll site to work with.

Much of this will also work for anyone using the Linux or Mac OS X versions of Docker Toolbox, but I’m approaching with a view to running this on Windows.

[migrate-to-jekyll]: {% post_url 2015-10-04-migrating-from-wordpress-to-jekyll %}

Install Docker Toolbox

Install Docker Toolbox. If you’re not familiar with Docker or Docker Toolbox, I highly recommend you go through their tutorials.

One note: If you already have VirtualBox installed or prefer to install it yourself, uncheck the VirtualBox option in the installer.

Create a Docker Machine

Docker Toolbox creates a couple of shortcuts for you. If you run Docker Quickstart Terminal, a machine called default will be automatically created for you.

Alternatively, you can create one with your own name from the command line:

docker-machine create --driver virtualbox dev

As you’ll see later on, I decided to go the route of managing this myself with my build script.

Create Dockerfile

To run Jekyll in Docker, you need to build a Docker image!

It struck me as simplest to build my own:

FROM debian:latest

# Install jekyll dependencies
RUN apt-get update && apt-get -y install \
  gcc \
  make \
  nodejs \
  python \
  ruby \
  ruby-dev \
  && gem install bundler

# Install site dependencies
WORKDIR /site
COPY Gemfile Gemfile.lock /site/
RUN bundle install

# Copy site files
COPY . /site/

CMD ["bundle", "exec", "jekyll", "serve", "--port=4000", "--host=0.0.0.0"]
EXPOSE 4000

I chose port 4000 as it isn’t either 8000 or 8080, which can be commonly used by other applications.

You’ll note that Gemfile and Gemfile.lock are copied first. Docker creates a layer out of each instruction (e.g. RUN, COPY) in the Dockerfile, and caches them. This means a layer only needs to be rebuilt if it or one of its parents has changed. These copied files are used to install your website’s dependencies, and since this is generally quite a slow operation compared to actually building the site, I opt to get the install done first and utilise Docker’s layer cache.

Create .dockerignore (and why it’s needed)

Docker builds work by archiving and sending the entire contents of the current working directly to the Docker daemon — the build does not happen on your local machine, but entirely on the remote machine.

This means that if you have many temp, cache or large files that are not needed for the build, they will needlessly slow the build down. to counter this, a .dockerfile can be created to tell the local Docker client which files the remote Docker daemon will not need.

.dockerignore works very similar to .gitignore. I’ll make a very simple one that ignores _site, just in case a build has happened locally:

_site

Create PowerShell build script

I created a build script, build.ps1, at the root of my website. In this build script I create a Docker machine (if it doesn’t exist) or start it (if it does), configure Docker to communicate with it, print out instructions on how to see the website, and finally build and serve it:

# Site building script for Powershell and Docker Toolbox
# Set the name of the machine
set machine dev

# Set up and/or start the machine -- note that create also starts the machine
docker-machine create --driver virtualbox $machine
docker-machine start $machine

# Set up Docker's env vars so it can connect
docker-machine env --shell=powershell $machine | Invoke-Expression

# We now need a means to connect to the website once it runs.  there are two methods:
# Method 1: Enable forwarding the port -- with thanks to http://stackoverflow.com/a/32175164
#   Only problem with this method is that it needs VBoxManage to be a part of the PATH -- this is a manual change.
#   Example address: http://localhost:4000/
#VBoxManage modifyvm $machine --natpf1 "jekyll,tcp,127.0.0.1,4000,,4000"
# Method 2: Connect to the docker machine's IP directly without port forwarding
#   Example address: http://192.168.99.100:4000/
docker-machine ip $machine | set ip
echo "Building website; will be available in a few moments. Press Ctrl+C when done."
echo "Connect to: http://${ip}:4000"

# Build and serve the website
(docker build -t blog .) -and (docker run -it --rm --publish=4000:4000 blog)

Note: The very last command, with the -and in the middle, will absorb all stdout, meaning that things will look very quiet. Rest assured that if the script doe not end, then things are working fine.

If you update the site and want to see what the changes look like, or are simply finished, press Ctrl+C and then re-run the build script as desired.

Profit

While not certainly not as easy to modify as when I’m on a Linux machine, this is certainly a big improvement on local testing than trying to run a Wordpress stack on Windows! I’ve also learnt a lot about docker-machine.

It may or may not be possible to mount the Windows website directory on to the docker machine in order to allow rebuilds on file modification, but I suspect that may turn out to be difficult, complicated or fragile, and the method described above works for me. If I change my mind, I’ll inevitably describe what I do!

Hopefully this guide helps you out, as well as future me!