Switching To Hugo: Part Two

This post is part two in a two-part series on switching my website to Hugo. It covers the technical details of hosting my website. For an explanation as to why I made the switch, read part one.

Hosting My Website

My website is hosted using caddy, a popular HTTPS server and reverse proxy. Caddy automatically obtains and renews TLS certificates for my domains, which makes TLS certificate management a breeze. The caddy server runs on one of my home servers, which itself runs NixOS.

Configuring the Web Server

NixOS allows for reproducible and declarative system configurations, which makes the process of configuring and deploying a web server quite trivial. One benefit of running my website on NixOS - aside from the fact that NixOS is my distribution of choice - is that I can copy the configuration down to any of my NixOS systems and have it work exactly the same, regardless of the server it’s running on. This alleviates the headache of dependency management and reduces setup times down to just nixos-rebuild switch, should I have the need to move my website to different hardware. The nix flake for my system configurations is tracked in a remote repository with git, making configuration changes a single git pull away.

The following module defined in modules/services/proxy/default.nix enables the caddy service using the caddy package found in nixpkgs, and opens up both TCP ports 80 and 443 to accept inbound HTTP/S traffic.

{ pkgs, ... }:
{
  services.caddy = {
    enable = true;
    package = pkgs.caddy;
  };

  networking.firewall.allowTCPPorts = [ 80 443 ];
}

Then, in a separate module defined in modules/services/web/default.nix, I’ve created a caddy virtual host entry for my domain name. By pairing the file_server and root directives together in caddy, I can spin up a web server and host the content found in /var/www/tdback.net/ on the NixOS server. I’ve also enabled both gzip and zstd support for compressing web server responses.

{ ... }:
{
  services.caddy.virtualHosts = {
    "tdback.net".extraConfig = ''
      root * /var/www/tdback.net/
      encode zstd gzip
      file_server
    '';
  };
}

The server is made publicly accessible using IPv6rs, which is a wonderful service I plan to cover in future blog posts. A few DNS host A and AAAA records are required to properly resolve queries for https://tdback.net to my web server’s IPv6 address (including requests made to the server using IPv4!) but that’s pretty much it in regards to the server configuration.

Writing and Managing Content

The following is a nix flake I’ve written to create a development environment on my desktop for managing the content and generation of my website.

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { ... }@inputs:
    inputs.flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import inputs.nixpkgs { inherit system; };
      in
      {
        devShells.default = pkgs.mkShell {
          buildInputs = with pkgs; [
            hugo
          ];

          shellHook = ''
            SITE="$HOME/projects/tdback.net"

            new-post() {
              hugo new "posts/$1/index.md"
              $EDITOR "$SITE/content/posts/$1/index.md"
            }

            del-post() {
              POST="$SITE/content/posts/$1"
              [ -d $POST ] && rm -r $POST
            }
          '';
        };
      });
}

When I run the command nix develop in my terminal, nix automatically creates a new shell environment, pulls down the hugo command from the specific revision of nixpkgs found in flake.lock, and defines the functions specified in the shellHook attribute.

By storing this flake and my website’s source code in a git repository, I can clone the repo down to an entirely separate machine - for instance my laptop - and have an entirely reproducible shell environment for working on my website.

I also have the added bonus of being able to completely blow away the temporary shell environment when I exit out of the terminal or type C-d (the same as exit). This means that hugo and any functions or environment variables defined in the shellHook attribute are only available when I’m in the temporary shell environment, thus reducing the clutter of programs, their dependencies, environment variables, and function definitions.

Deployment of the Site

When I make a change to the website, the entire site can be regenerated and deployed to the NixOS server by running the following one-liner:

hugo && rsync -avz --delete public/ hive:/var/www/tdback.net/

I’ve placed this command in a shell script to make deployments as simple as typing ./deploy.sh. This results in near-zero downtime between changes: a simple refresh of the page on a client’s device will load the newest version of the site.