Hugo & Docker
Hugo is a great tool for static site generation, it’s how this site is being generated. Super fast thanks to Go and very flexible with it’s layout and templating system. What could possibly go wrong.
Despite the feature set Hugo offers, they stopped updating their docker image in 2017, almost 8 years ago. That sucks.
Searching the web for anything Hugo and Docker related brings up the old images from Hugo as well as the new unofficial images by Hugomods. I thought to myself this is great, I can spin up a container with this image, point the /src
to my static site I want and have it serve up the pages automatically. While that was a good idea, it turns out the built in HTTP server, hugo server
, is exclusively development only because they force all URLs to be local ones, http://localhost:1313/
.
Even when you change the baseURL
in Hugo’s config.toml
, it’s mostly ignored for development.
Solution⌗
While Hugo themselves stopped supporting it and Hugomods doesn’t offer a automatic build system, it’s not all too hard to make your own Docker image to do the basics.
The image needs to have Hugo, obviously, some kind of HTTP Server and a way to monitor for changes to trigger regeneration.
So here is the Dockerfile I came up with.
FROM hugomods/hugo:exts-non-root
First you will notice I am using Hugomods’ Hugo image. This is because they offer up to date Hugo images for Docker and I don’t need to reinvent the wheel here.
USER root
RUN apk add --no-cache caddy inotify-tools
Next it switches over to root to install Caddy and inotify-tools
. Caddy is my HTTP Server of choice here, you’ll see why later, and inotify-tools
will be used for monitoring some folders and the config file for changes.
COPY ./Caddyfile /etc/caddy/Caddyfile
COPY ./watch.sh /watch.sh
RUN chmod +x /watch.sh
Then it copies the Caddyfile
and watch.sh
files into the image for use. (You can find these files at the end of the post, as well as the full Dockerfile)
USER hugo
WORKDIR /src
EXPOSE 80
CMD /watch.sh & caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
And lastly, switching back to the hugo user account, set the work directory to /src
, expose the HTTP port 80 for Caddy and run the watch script and caddy at the same time.
The Caddyfile
file is just a very simple config file that tells Caddy to listen on port 80 and serve everything from /src/public
as HTTP using the built in file server module, it’s only 3 lines long which likely beats out any other HTTP Server software for configuration simplicity.
The watch.sh
file triggers an initial build at startup.
Complete files⌗
Dockerfile - Click to open
FROM hugomods/hugo:exts-non-root
USER root
RUN apk add --no-cache caddy inotify-tools
# Copy static config files
COPY ./Caddyfile /etc/caddy/Caddyfile
COPY ./watch.sh /watch.sh
RUN chmod +x /watch.sh
USER hugo
WORKDIR /src
EXPOSE 80
CMD /watch.sh & caddy run --config /etc/caddy/Caddyfile --adapter caddyfile
Caddyfile - Click to open
:80
root * /src/public
file_server
watch.sh - Click to open
#!/bin/sh
echo "Starting Hugo build watcher..."
echo "Triggering initial build..."
hugo --minify
while inotifywait -r -e modify,create,delete \
--exclude '(\.git|public)' /src/content /src/layouts /src/static /src/config.toml; do
echo "Change detected, rebuilding..."
hugo --minify
done