Docker for python pelican blog with nginx

Posted on 29 May, 2019 in Products

TL;DR

Dockerised pelican blog with deployment of static pages to nginx.

dockerfile:

FROM python:3.7-alpine as base

# container setup.
FROM base as builder

RUN mkdir /install
WORKDIR /install
COPY . ./

# Install language pack.
RUN apk --no-cache add ca-certificates wget && \
    wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-bin-2.25-r0.apk && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-i18n-2.25-r0.apk && \
    apk add glibc-bin-2.25-r0.apk glibc-i18n-2.25-r0.apk glibc-2.25-r0.apk

# Set the lang.
RUN cat locale.md | xargs -i /usr/glibc-compat/bin/localedef -i {} -f UTF-8 {}.UTF-8
ENV LANG=en_GB.UTF-8 LANGUAGE=en_GB.UTF-8

# pelican site setup & generate.
RUN pip install --no-cache-dir -r requirements.txt
RUN python feedsreader.py
RUN pelican content -s publishconf.py

# default nginx, copy in static output.
FROM nginx
COPY --from=builder /install/public /usr/share/nginx/html/

Code structure:

Code structure

Introduction

Since putting this blog on gitlab pages with pelican I haven't written an awful lot. Gitlabs hosting has served me well with minimal overhead and its use of docker containers for the CI/CD builds spurred me to learn docker. I'm back with an entry on how to dockerise this blog.

My goal was to have docker with python run pelican and push the output html files to a simple web server. It turned into quite the task!

Discussion

It was easy enough to have a python docker image and have pelican run to create the static output:

FROM python:3.7-alpine

COPY . ./

RUN pip install --no-cache-dir -r requirements.txt
RUN pelican content -s publishconf.py

However, I had two issues to resolve: the pelican command resulted in a Locale warning and how to show the static content?

The Locale warning is shown because the python alpine images do not use locale. After lots of searching and trying code snippets in my dockerfile, I found a fix to install language packs to an alpine image and to set the locale using it:

RUN apk --no-cache add ca-certificates wget && \
    wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-2.25-r0.apk && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-bin-2.25-r0.apk && \
    wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.25-r0/glibc-i18n-2.25-r0.apk && \
    apk add glibc-bin-2.25-r0.apk glibc-i18n-2.25-r0.apk glibc-2.25-r0.apk
# Set the lang.
RUN cat locale.md | xargs -i /usr/glibc-compat/bin/localedef -i {} -f UTF-8 {}.UTF-8
ENV LANG=en_GB.UTF-8 LANGUAGE=en_GB.UTF-8

To serve the static files output by pelican, I had used the default python webserver command:

CMD ["python3", "-m", "http.server"]

But there are unmissable warnings not to use this in a production environment. I searched around for a suitable one line replacement that was fit for production, but I could not find a simple solution that didn't immediately get used with an nginx proxy, which was more than I needed.

In the end I chose uwsgi and had to create a small flask program, which promised production-level performance from the off, and I was reasonably satisfied with that.

However, after working on some .net docker files I noticed that the default Visual Studio -> Add Docker Support, dockerfiles used layer caching and then copied files between the containers/layers. I realised I could do this with the python and nginx images in the same dockerfile. This isn't contrary to best practice because each container was still only responsible for one thing: python-alpine for build and nginx for deployment. I then added the copy command to nginx:

FROM python:3.7-alpine as builder
.
FROM base as builder
.
FROM nginx
COPY --from=builder /install/public /usr/share/nginx/html/

This also keeps the final image file to be deployed down to ~100MB. A dead-end option I looked at was adding nginx to a standard python image but that resulted in a bloated ~450MB image size. I also tried to install nginx to the python alpine image but nginx then failed with a 'pid' issue. Similarly a large image file size was the result when I installed python and pelican requirements to the default nginx image.

Conclusion

Easy when you know how but there wasn't an awful lot of information around how to keep it simple, i.e., without a more complex proxy set-up used in the other examples I read. I'm now happy with the lighweight docker image I generate for deploying my blog.