Running a Pelican Blog on Docker Cloud with Let's Encrypt

Posted by Dave on 18 January 2017

It's 2017 and my blog was starting to look a little dated so I decided it was time for a face-lift. While I was at it, I overhauled the way I deploy my blog too.

The Old Stack

My old stack was based on Pelican and deployed using Dokku. Pelican is a static site generator written in Python with a vibrant community of themes and plugins - if you aren't using it already you should check it out. Dokku allows you to have a Heroku-like workflow but using Docker. In principle, this sounds good... but after a few years of using it I have to say that I'm not a fan. My main issues are:

  1. You publish by git push-ing directly to your server so master and what's running aren't always in sync.

  2. All the build happens in a buildpack on the server-side. There is no way to test what it actually does and it failed catastrophically for me a few times.

  3. Everything was being served over plain old HTTP

The New Stack

Being a container aficionado, I decided that everything should be containerized and running on Docker Cloud. I also wanted HTTPS so I was in need of a certificate. Let's Encrypt to the rescue! All I needed now was to replace the buildpack with a Dockerfile.

Here is what I came up with:

FROM python:3-alpine
RUN apk add --no-cache nginx
RUN pip install pelican markdown "ipython=2.4.1"

ADD . /var/www/blog
WORKDIR /var/www/blog
ARG DEV
RUN pelican content -o output -s pelicanconf.py
RUN cp -f nginx.conf /etc/nginx/nginx.conf

CMD nginx

EXPOSE 80

There are a couple of smart things I'd like to point out here.

  • Rebuilds are quick as everything up until the ADD instruction is cached
  • The flag --build-arg DEV=1 can be used with docker build to generate relative URLs for local testing
  • This image is pretty small at 270MB considering what's packed in. Why IPython you may be thinking? That's another blog post in the making...
  • The nginx.conf is very simple. It basically serves /var/www/blog on port 80.
  • "Why port 80? That's not HTTPS..." Don't worry. All will be explained in time...

So with image, means of obtaining a certificate, and Docker ID in hand I logged on to Docker Cloud.

Docker Cloud

First thing I had to do was link the private GitHub repo that contains my blog and set up automatic builds. This was really easy to do by simply following the wizard.

I then created a Node Cluster and "brought my own node" to Docker Cloud as I use Mythic Beasts for all of my hosting - they are awesome. I update my DNS to make sure that my blog dtucker.co.uk pointed to the IP address of my Docker Cloud node. Once this was completed, all I needed to do was define a Stack!

This whole Let's Encrypt thing is pretty complicated, especially when containers are concerned. As certificates expire every 90 days, we need some means of requesting a new one without interaction especially as my mean time to blog post is usually > 90 days. It also requires a magical directory to be served on port 80 for domain validation. This means I'm going to need:

  • A container to deal with getting certificates from Let's Encrypt
  • A container to serve my blog
  • A proxy container so network connections go to the right container!

It just so happens that some smart cookie has solved this proxy problem already and it's really easy to use!

Here is my completed Stackfile:

blog:
  autoredeploy: true
  environment:
    - FORCE_SSL=yes
    - 'VIRTUAL_HOST=*,https://*'
  image: 'davetucker/blog:latest'
haproxy:
  image: 'interaction/haproxy:master'
  links:
    - blog
    - letsencrypt
  ports:
    - '80:80'
    - '443:443'
  roles:
    - global
  volumes_from:
    - letsencrypt
letsencrypt:
  environment:
    - DOMAINS=dtucker.co.uk
    - [email protected]
  expose:
    - '80'
  image: 'interaction/letsencrypt:master'

I clicked the "Start" button and then my site was alive and kicking.

Conclusion

When I write a new blog post, I now just git push and Docker Cloud takes care of the rest. It builds the blog container and automatically re-deploys when the new version is created! Everything is secured with HTTPS courtesy of a Let's Encrypt certificate which is magically renewed so it should never expire.

I also have a great writing/test workflow too!

If you want more content like this, want to know what IPython was for or want to say "Hi!" please leave a comment below or ping me on the social network of your choosing!

@dave_tucker