Build Docker image for site builds that allow for separately configurable Hugo and Go versions

The Technical Writing team maintains a "site building" Docker image that's configured at: https://gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/-/blob/main/dockerfiles/docs-gitlab-com-builder.Dockerfile?ref_type=heads.

At the moment, we can only specify a version of Hugo, with the version of every other dependency set by the upstream maintainers of the base Docker image we build from. This seemed ok at the time because we were far more concerned about pinning Hugo versions than Go versions.

However, this reduces our ability to move forward with other dependencies while being able to be extra conservative with Hugo dependencies. This doesn't seem like very much of a problem, but does block issues like !1136 (merged). We try in this project to keep our dependencies aligned with dependencies in other projects. Many projects have moved to Go 1.24:

Even in this project, we locally use Go 1.24: https://gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/-/blob/b3b8a0efcc6afefd57116ddd4642da6d0c1184e0/mise.toml#L8. It's only in our site builds that we're stuck with Go 1.23:

  1. Run docker run --rm -ti registry.gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/docs-gitlab-com-builder:hugo-0.145.0 sh.
  2. In the running container:
    /src # go version
    go version go1.23.8 linux/amd64

Proposal

I propose that we refactor the builder Docker image so that it can be tagged with independent versions for:

  • Go
  • Hugo
  • Node

So images would be tagged:

  • Rather than: registry.gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/docs-gitlab-com-builder:hugo-0.145.0
  • Would now be: registry.gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/docs-gitlab-com-builder:go-1.24-hugo-0.145.0-nodejs-22

As now, these versions would be derived from settings in .gitlab-ci.yml: https://gitlab.com/gitlab-org/technical-writing/docs-gitlab-com/-/blob/b3b8a0efcc6afefd57116ddd4642da6d0c1184e0/.gitlab-ci.yml#L16.

The process for building an image would be exactly the same, and the functionality of the image would be exactly the same.

Using mise

One was to achieve this is to build the image using a more basic image, and pulling the tooling directly from each project.

Another possible way is to introduce mise into the Docker image as an installation and execution method. We wouldn't try and reuse the mise.toml file in the project, but rather use mise to install dependencies with the versions we pin at build time.

For example, with a basic image that's had mise installed, installation could be:

  • mise --quiet use --global --pin go@${GO_VERSION} for Go.
  • mise --quiet use --global --pin hugo-extended@${HUGO_VERSION_NEXT} for Hugo.
  • mise --quiet use --global --pin node@${NODE_VERSION} for Node.js.

Some guidance for running mise in pipelines is available at: https://mise.jdx.dev/continuous-integration.html, but I'm sure we could also work with mise very similarly to how we work with it locally.