20 February 2020
Well, the story is as follows. I maintain two websites, this one, and another one, that are generated with Jekyll. Every time I tried to update the other one (not amytabb.com), things were broken, I would have to fight with Ruby – something I know very little about – and a planned 5-minute update ended up consuming an inordinate amount of time. Since I update from more than one machine, my thinking was that I could freeze the Ruby version once and for all, and just run Jekyll from a container.
Getting a Docker image set up for amytabb.com was easy. Getting a Docker image set up for the other website never worked – and finally, I realized I would need to start with a template with fewer dependencies. Since I have very little on that other website (I am purposefully not linking to it …), this is not a big deal and will save me time and angst in the long run.
In the meantime, I had gotten a container working, so why not write a blog post? If you’ve been having a hard time getting Jekyll working, or there are incompatibilities with versions, this method should work no matter what OS you have. Technically, since Jekyll will be running from the container, even though I use an Ubuntu image, it should run.
If you don’t have Docker already, you’ll need to install it. Here’s the link to install for Linux, and I also endorse uninstalling old versions if you have them floating around.
The image is currently at Docker hub, amytabb/jekyll-basic. To pull to your local disk,
docker pull amytabb/jekyll-basic
Alternatively, you can build the Docker image on your local disk yourself. The Dockerfile is in a Github repository, amy-tabb/docker-jekyll-basic
git clone https://github.com/amy-tabb/docker-jekyll-basic.git
cd into the directory, and then build:
sudo docker build -t jekyll-image .
Now it is time to run Jekyll from the container. We need a lot of stuff here. Assuming that
jekyll-image is the image name (it would be
amytabb/jekyll-basic:latest if you pulled it from Docker hub),
sudo docker run --network host -v /home/atabb/git/amy-tabb.github.io:/write_directory -it jekyll-image jekyll serve
is one concrete example. I’m going to put in another example, because I myself use this post a lot.
Case where you are in the directory, and you are using the container from the Docker hub:
sudo docker run --network host -v $PWD:/write_directory -it amytabb/jekyll-basic:latest jekyll serve
Substituting in the generics:
sudo docker run --network host -v /full/file/path/of/web/site:/write_directory -it jekyll-image jekyll-commands
Let’s start at the back – the jekyll commands.
For some websites, the commands are
bundle exec jekyll serve. The template I am using is generated with
jekyll serve, so I used that.
jekyll-image is the name of the docker image, again, you would use whatever name you selected when you built the image locally, or
amytabb/jekyll-basic:latest if you pulled it from Docker hub.
-v /full/file/path/of/web/site:/write_directory means to bind the first path on the host machine, to the second path in the image (separated by a
:). I set up the Dockerfile such that the working directory is
/write_directory, so that will not change.
-v indicates a bind mount (a type of mount in Docker).
--network host means to allow the container to access the host’s network. From the docs, this is only available in the Linux Docker version. But I really don’t know what the status is for other OS’s. I have complained about Docker’s docs before.
run: we’re running the image to get a container.
FROM ubuntu:18.04 RUN apt-get update RUN apt-get -y upgrade RUN apt-get -y install ruby-full build-essential zlib1g-dev RUN apt-get -y install ruby-bundler RUN useradd jekylluser RUN mkdir /home/jekylluser RUN mkdir /write_directory RUN chown jekylluser /home/jekylluser USER jekylluser WORKDIR /home/jekylluser/ ENV HOME "/home/jekylluser" ENV GEM_HOME "$HOME/gems" ENV PATH "$HOME/gems/bin:$PATH" RUN gem install github-pages RUN gem install bundler WORKDIR /write_directory/
I’ll explain each part. For more in-depth details about using Docker for research purposes, I have a tutorial that concerns using Docker to package code for those not on your OS – which in my case, is C++ with Linux.
FROMcreates a layer from the
ubuntu:18.04image. This image is already created on Docker hub.
RUN ...runs instructions as we would from a bash shell, in this case:
upgrade. Is this necessary? Maybe not for this particular project, but for code, it is.
useradd jekylluser: adds a new user, as in Docker we only have a root/superuser by default.
mkdir /home/jekylluser: we create a home directory for the new user.
mkdir /write_directory: we create a working directory for Docker, while we still have root access.
chown jekylluser /home/jekylluser: give the new user access to their home directories.
USER jekylluser: switch user to the new user.
WORKDIR /home/jekylluser/switch to the new user’s home directory. I learned the hard way
cdis not your friend in Docker: Docker: What’s the deal with WORKDIR?.
ENV: set environment variables, which is similar to the instructions relevant to
./bashrcfrom the Linux installation for Jekyll page.
RUN..: I install the
github-pagesgem, and then install the
bundlergem. You could add more gems if needed.
WORKDIR /write_directory/: set the working directory to
/write_directory/. This is important as we’ll bind a directory on the host machine to this particular directory in the Docker image, so we need to make sure that the final
WORKDIRis the second argument of
Final note: beware of relative and full paths in Docker. There are many ways to get things wrong. Docker: What’s the deal with WORKDIR? has a full explanation of these phenomenon, and to eliminate problems, I use full paths (starting with
/). In short, if there’s a way to get it wrong, I’ve probably done it!
Hopefully, you can see there’s plenty of ways you can alter this basic Dockerfile for custom cases, to install more gems or to extend functionality.
© Amy Tabb 2018 - 2021. All rights reserved. The contents of this site reflect my personal perspectives and not those of any other entity.