Docker Multi-stage Builds for Static Sites

on Docker,

Building this website on Jenkins every time it needs to be deployed is a little bit tricky.

  1. node-sass requires Node version 7 or below. Currently, Node 8 is the latest LTS, and Node 9 is stable. So, installing a Node version manager like nvm provides a Node 6 runtime that Jenkins can use.

  2. hugo on Debian’s Apt package manager is not recent enough to build correctly. On the other hand, using Snap packages via snap install hugo has its own issues. Snap’s confinement model forbids access to Python Pygments library necessary for code-highlighting.

Essentially, dependencies over three different languages need to be present in order for yarn run build to work.

Simple Dockerfile

This simple Dockerfile installs all the dependencies in a container, then builds the website.

FROM node:6-wheezy
MAINTAINER Nicholas Luo <info@nicluo.com>

# Install pygments (for syntax highlighting)
RUN apt-get -qq update \
    && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y --no-install-recommends python-pygments \
    && rm -rf /var/lib/apt/lists/*

ENV HUGO_VERSION=0.30.2
ADD https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz /tmp
RUN tar -xf /tmp/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz -C /tmp \
    && mkdir -p /usr/local/sbin \
    && mv /tmp/hugo /usr/local/sbin/hugo \
    && rm -rf /tmp/hugo_${HUGO_VERSION}_linux_amd64

RUN mkdir /source
WORKDIR /source
COPY ./ /source/

RUN ["yarn", "install"]
RUN ["yarn", "run", "build"]

The commands to build the container image, then copy out the build results - the website html and minified assets:

docker build -t victor-hugo .
docker create --name hugo-build victor-hugo
docker cp hugo-build:/source/dist/ ./dist/
docker rm hugo-build #remove build container

Multi-stage builds

Docker multi-stage builds are important to keeping container sizes small. For example, with Go executables, usually the Go compiler is not needed at all in the final container image.

Static site generators are actually a perfect use case for Docker multi-stage builds. A lot of dependencies are installed to generate a static site, but very little if not none of those are required to serve the website.

Running docker history will show the size contribution from each command to the container image. Note especially, the size of the COPY and Yarn commands, contributing 263MB and 114MB respectively. Much of that is in the node_modules folder, which is not needed after the site assets are built.

$ docker history victor-hugo
IMAGE               CREATED             CREATED BY                                      SIZE
3b6dcdf37166        5 minutes ago       yarn run build                                  4.83MB
d057a96f6766        5 minutes ago       yarn install --force                            114MB
13676adca82e        7 minutes ago       /bin/sh -c #(nop) COPY dir:82823667b03d170...   263MB
d1d879eef395        7 minutes ago       /bin/sh -c #(nop) WORKDIR /source               0B
8d2f1c9778ce        7 minutes ago       /bin/sh -c mkdir /source                        0B
c965b0397ec0        7 minutes ago       /bin/sh -c tar -xf /tmp/hugo_${HUGO_VERSIO...   27.1MB
d8c25bd57550        7 minutes ago       /bin/sh -c #(nop) ADD 97e9e04986ac64010d85...   5.76MB
66a9abcd1b53        7 minutes ago       /bin/sh -c #(nop)  ENV HUGO_VERSION=0.30.2      0B
3fa52be6f6b1        7 minutes ago       /bin/sh -c apt-get -qq update  && DEBIAN_F...   5.99MB
5e4d3322fadd        11 minutes ago      /bin/sh -c #(nop)  MAINTAINER Nicholas Luo...   0B

To setup a multi-stage build, simply add the following lines:

FROM node:6-wheezy as builder
MAINTAINER Nicholas Luo <info@nicluo.com>
...
RUN ["yarn", "run", "build"] 

FROM node:6-wheezy

COPY --from=builder /source/dist /source/dist
WORKDIR /source

The results are obvious, from 420MB to 43MB is a whopping 90% decrease in space used, and bandwidth if the container images have to be uploaded to a Docker registry.

$ docker history victor-hugo
IMAGE               CREATED             CREATED BY                                      SIZE
bcc379bccc16        9 seconds ago       /bin/sh -c #(nop) WORKDIR /source               0B
89c68c85f277        9 seconds ago       /bin/sh -c #(nop) COPY dir:8735bd99faa48a9...   43.2MB
41267e55be53        10 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B