The proper way to build a Hugo docker container

June 02, 2022 1 minute

I’m writing this article since a cursory internet search when creating this blog revealed numerous terrible ways to build hugo.

A hugo deployment needs nothing other than an unprivileged webserver and the static html files.

Without further ado this is the most efficient hugo Dockerfile you will find:

FROM --platform=$BUILDPLATFORM alpine:3.13 as build
RUN apk add --no-cache hugo
WORKDIR /src
COPY . .
RUN --mount=type=cache,target=/tmp/hugo_cache \
    hugo

FROM nginxinc/nginx-unprivileged
COPY --from=build /src/public /usr/share/nginx/html

EXPOSE 8080

This is a Dockerfile defines multi-stage build which results in a final image that does not include hugo itself, but only the blog. It runs nginx in unprivileged on port 8080 and as the non-root user for that extra bit of hardening.

It’s also the most efficient multi-arch build you will ever see. Why? The secert lies in the --platform=$BUILDPLATFORM from argument. The docker buildx (buildkit) container builder usually runs each stage with each and every architecture. This, although very quick anyway, is wasteful as the generated web resources are not architecture dependent. Specifying –platform=$BUILDPLATFORM tells buildkit to not bother emulating a different archictecture for that stage.

The final improvement is using --mount=type=cache,target=/tmp/hugo_cache in order to reuse hugo cache between builds. As if builds weren’t quick enough!

Many thanks to Tõnis Tiigi, the buildkit developer who made this possible.