Why Hugo

Once I learned how to code, like every other person who doesn’t know what they’re doing, I made a jekyll site on GitHub. It was fine. But I didn’t like jekyll.

I have built nearly a dozen sites in Gatsby, but I’m bored with over-engineering all of my stuff with JavaScript. I wanted to go with something that built faster and didn’t absolutely require users to load JS “for optimal performance.” I just want to send html damnit.

I thought about doing something straight from emacs, as I quite like emacs (I’m typing in it right now), but despite lacking the high-level JS build baggage, it comes with low-level config baggage that–despite its lightweightness–is, frankly, esoteric.

I haven’t worked with Hugo before, so that’s a point in its favor. It’s fast to build, which is also a nice change for me (Gatsby is slooooowwwwwww). And since it’s a Go binary, installing didn’t require a single dependency outside of Hugo.

Here are the details on how I’m running it, as I think my setup is a little more unique.


High-level, the stack is

  • Alpine Linux!
  • Hugo!
  • Nginx!
  • OpenRC!

Step 1: I’m on Alpine, so for Hugo I just need to reach straight for a binary here. I take the extracted hugo binary, copy it to /usr/local/bin/hugo, then chmod a+x /usr/local/bin/hugo, and it’s off to the races!

My plan was to actually serve hugo from hugo server command. Why? I have no idea. Just kidding. It just… seemed cool to me? As I’m writing this I’m refreshing on a live site and it’s there because hugo server is meant for live development, not production. I’m just so bored with build processes, especially when it’s to fix a damn typo. So, the following steps explain how to serve from the hugo server command using OpenRC and nginx on Alpine.

Step 2: This site exists next to my pleroma instance on the tld, for which I had already set up nginx as a reverse proxy. The nginx config for this site is as follows:

## /etc/nginx/conf.d/

server {
  listen *:80;
  include conf.d/;
  return 301 https://$server_name$request_uri;

server {
    listen 443 ssl http2;
    include conf.d/;

    ssl_session_timeout 5m;

    index index.html;
    ssl_trusted_certificate   #fullchain goes here
    ssl_certificate           #fullchain goes here
    ssl_certificate_key       #private key goes here
    location / {
      proxy_pass                      http://localhost:1313;

This is a really bare bones nginx setup which accepts both http and https, while redirecting all http requests to https. On alpine, as of this writing, the notable certbot library for helping with ssl certs is completely busted. Instead, one can use this guide to set things up with acme (hence the include conf.d/

Also worth noting I left a line in from when I was testing this subdomain (index index.html) and that line does nothing, but I copy pasted it regardless because this is my current working nginx config.

The line proxy_pass http://localhost:1313; is the line which is passing requests from to the hugo server which, by default, runs on port 1313, bound to localhost. If you were to keep a command line open forever with the hugo server command running (with the proper flags), you’d be done and this would all work! But… that’s not a good idea.

Step 3: We need to run our hugo code as a service, rather than just in the command line. At this point I’m taking for granted that you’ve got ~/some_hugo_project that you can pop in and run hugo server on. We’re going to make an init.d service using OpenRC so that whenever our linux server starts up, it will start up our hugo server.

touch a file called somehugo and have it look like the following:


# Requires OpenRC >= 0.35

command_args="server -b --appendPort=false"

# Ask process to terminate within 30 seconds, otherwise kill it
retry="SIGTERM/30 SIGKILL/5"


depend() {
    need nginx

For anywhere you see user, this refers to the system user in whom’s home the hugo code resides. The command_user should be the same since it’s their directory. As for command, make sure that if you put hugo somewhere else (such as in a more specific /bin) that you link it there.

As for the command_args, you will run into the following problem if you don’t specify the base_url (that’s via -b) or if you turn off appendPort: your auto-generated /sitemap.xml will say that all of your pages reside at localhost:1313. I did not know about the appendPort option until I took to the forums. Because hugo server is meant for dev, of course it says localhost is your baseURL. What was more annoying though was, even when that was changed, the :1313 was still there, which is patently false given the reverse-proxy config. Anyway, these args resolve that issue. Everything else is just OpenRC biz.

Save this, then copy it into /etc/init.d and chmod it. Then, start the service with sudo service somehugo start and that’s that.

Step 4: Regret.

Just kidding.

It’s done, this works, what you’re reading right now is a result of it. The question is, is it worth it to have resources dedicated to listening to changes in files when for 99% of the time I won’t be making changes in files?

The answer is obviously no. It’s not worth it.

But it’s cool and I’m paying for a resource ceiling, not by resources used, so unless it affects pleroma or I want to install another service, it’s not my hardware or my problem! Until it causes an issue for my workflow, the live reload alone is superior to every single build flow I’ve ever used for a static site gen.

EDIT: Go ahead and add --disableLiveReload to the service script, or else you will have pointless websocket connections opening looking to live reload on however-many-people’s computers. It sounds cool but it’s just a waste.