Dockerizing a Gatsby Site

Just recently I started to migrate my blog from Hugo to Gatsby. Gatsby is also a static site generator but it is a lot more flexible than others. While a generator like Hugo only provides a way to build a static site based on markdown files, Gatsby is more like a framework to build whatever you want. In exchange the learning curve of Gatsby is a bit steeper, but they make up for it with excellent documentation.

After the first version of my new blog was ready I started to look for a way to build a Docker image for it. During my search I found the gatsby-docker project at GitHub, but it doesn’t seem to be maintained so well. When I tried the instructions from the Readme I realized that the images on Docker Hub do not really align with the git repository. According to the docs the onbuild tag should contain the tools, e.g. gatsby-cli, to build a Gatsby page, but it doesn’t. Instead it contains the same nginx server like the latest tag, which means the instructions of the Readme don’t work. By now there is also an upstream issue for this.

Docker multi-stage build

So without working upstream images I decided to build my own:

FROM node:13-buster-slim as build

RUN yarn global add gatsby-cli && gatsby telemetry --disable
ADD package.json yarn.lock ./
RUN yarn --production --frozen-lockfile --non-interactive

ADD . ./
RUN gatsby build

FROM abiosoft/caddy:1.0.3-no-stats
RUN echo 'tls off' >> /etc/Caddyfile
COPY --from=build /app/public /srv

This Dockerfile uses the multi-stage feature of Docker. This means that the build process consists of several stages which build seperate Docker images. In this case there are two stages. The first stage uses the official Node.js image, installs Gatsby and all dependencies of my blog and in the end actually builds the static Gatsby site. Due to all the Node.js dependencies which are required to build the site, the resulting container of the first stage is more than 800mb big.

But none of these dependencies are required to serve the static site, to be precise Node.js and Gatsby aren’t required either. That’s where the second stage comes in handy. It builds a Docker image based on a popular Caddy image. The multi-stage feature of Docker makes it possible to use the --from parameter in the COPY command to copy the content of /app/public from the previous stage to /srv in the current stage. This way the final Docker image does not contain all the build dependencies, but only the actual static site and a minimal webserver. For my blog this shrinks the image from over 800mb to less than 70mb. In the second stage you can also use another base image, so in case you don’t want to use Caddy you can use nginx, like the mentioned gatsby-docker project, instead.

The smaller Docker images does not only save some disk space, it also makes pushing and pulling it to a Docker registry faster, which means faster deployments. Removing unnecessary software from the final image is also always a good idea to reduce the potential attack surface (though with a static site this doesn’t matter too much).