To use private modules when deploying a Hugo site from GitHub to Netlify or Cloudflare Pages, you’ll probably run into trouble to get the services to fetch the modules. This note describes a workaround.

Obstacles

  • The authorization you give the build service to fetch private GitHub repositories doesn’t transfer to the build process, which is when Hugo tries to fetch modules.
  • The build command on Netlify could clone a second module from GitHub through a deploy key, but not a third or fourth, because GitHub doesn’t allow assigning the same deploy key to more than one repository.

The usual workaround is a “machine user” — an ad-hoc GitHub account with access to all involved repositories — which is more overhead that one may want to put up with.

While the strategy described here isn’t free of overhead, it’s limited to the build, no maintenance of external configuration is involved.

Strategy

  1. Generate one deploy key for each module
  2. Pass deploy keys to the build through environment variables
  3. Use git instead of Hugo to fetch modules
  4. Use Hugo’s module replacements feature to point Hugo to the locally cloned modules

Implementation

Say you want to use modules https://github.com/user/mod1 and https://github.com/user/mod2 in a Hugo web site.

Generate a key pair for each module:

ssh-keygen -q -N -f mod1.key
ssh-keygen -q -N -f mod2.key

Reformat the private key to a single line by replacing newlines with underscores:

tr '\n' '_' < mod1.key
tr '\n' '_' < mod2.key

In Netlify or Cloudflare Pages build settings, create environment variables named SSH_KEY_mod1 and SSH_KEY_mod2 and copy/paste the content of the respective keys.

Create environment variables to tell the builder to use Go and Hugo versions that support modules:

  • GO_VERSION: 1.15
  • HUGO_VERSION: 0.79.0

Add a build.sh file to your site repository and make it executable:

#!/bin/bash
set -e

# Copy the key for mod1 from the environment to the filesystem
echo -e "${SSH_KEY_mod1//_/\\n}" > /tmp/mod1.key
chmod 600 /tmp/mod1.key
echo -e "${SSH_KEY_mod2//_/\\n}" > /tmp/mod2.key
chmod 600 /tmp/mod2.key

# The .gitconfig file on Cloudflare Pages forces https connections to github;
# undo that by telling git to not use .gitconfig.
export GIT_CONFIG=/dev/null

# Clone mod1
export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no -i /tmp/mod1.key"
git clone git@github.com:user/mod1 /tmp/mod1

# Clone mod2
export GIT_SSH_COMMAND="ssh -oStrictHostKeyChecking=no -i /tmp/mod2.key"
git clone git@github.com:user/mod2 /tmp/mod2

# Tell Hugo to use the locally downloaded mod1 and mod2
export HUGO_MODULE_REPLACEMENTS="github.com/user/mod1->/tmp/mod1,github.com/user/mod2->/tmp/mod2"

# Run Hugo
hugo

Limitations

This strategy uses the latest version of every module and the script needs to be updated when modules are added or removed. It should be possible to use go.mod instead of hardcoding modules by somewhat overengineering extending build.sh.

References