/ Compiler says no!

Quick static site deployment

A combination of hugo and nix can be used to have an automated site deployment for free in under 40 lines of code.

The most sane deployment for a simple website is having a bunch of HTML and CSS files and uploading them to a web host1, and this 90s style has made a comeback because it brings with it fast loads, good caching, low CPU usage, simple deployment and security without any effort. We can modernize it a bit to gain access to more modern convenience like automaticed deployments, but we need to justify each addition’s complexity.

Raw HTML can be a bit cumbersome and a static site generator is a good value for the added complexity. From the plethora2 of options we’ll go with hugo due to its popularity. We’ll also add an automated build including dependencies using nix, which lets us share development and deployment configurations and has been introduced here before. Finally we will need a CI to automate builds, and a host, for which we’ll pick GitHub3 Pages for now, as that is where the source already lives.

Writing the nix derivation is easy enough:

{ pkgs ?
  import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/22.11.tar.gz")
  { } }:
pkgs.stdenv.mkDerivation {
  name = "compilersaysno";
  version = "1";
  nativeBuildInputs = with pkgs; [ hugo ];
  src = ./compilersaysno;
  buildPhase = "hugo";
  installPhase = ''
    mkdir $out
    cp -r ./public $out/
  '';
}

We’re putting hugo into nativeBuildInputs here instead of just using ${pkgs.hugo}/bin/hugo in the buildPhase script, as we can just nix-shell into it as well when writing this way. Note that pkgs is pinned to 22.11 for reproducibility. At the end, we will get a built site in result/public.

The last step is uploading to GitHub pages, for which we leverage the upload-pages-artifact and deploy-pages actions in .github/workflows/ghpages-publish.yml:

name: Build and upload to GitHub pages
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: cachix/install-nix-action@v20
      - uses: actions/checkout@v3
      - run: nix-build
      - uses: actions/upload-pages-artifact@v1
        with:
          path: result/public
  deploy:
    needs: build
    permissions:
      pages: write
      id-token: write
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - uses: actions/deploy-pages@v2

We keep it simple with an action on every push, you may want to adapt this to your personal workflow, e.g. by setting

on:
  push:
    branches:
      - master

instead. Not shown here is the GitHub pages setup, which unfortunately costs money if the repo is private.

The true 90s experience: Uploading files directly

When using a different host like Hetzner or Netlify, uploading files directly is a good alternative and by not opting into the automated flow of these providers it becomes easy to switch hosts later.

The third-party action-files allows for straightforward rsync + ssh based deployments, thus we can swap out our entire workflow like this:

name: Publish blog
on: [push]
jobs:
  build_and_publish:
    runs-on: ubuntu-latest
    steps:
      - uses: cachix/install-nix-action@v20
      - uses: actions/checkout@v3
      - run: nix-build
      - run: mkdir -v ${{github.workspace}}/output && cp -rv result/public ${{github.workspace}}/output/
      - uses: 49nord/action-files@v1
        with:
          src: ${{github.workspace}}/output/public/
          dest: www_compilersaysno@compilersaysno.com:/home/www_compilersaysno/www/
          ssh_key: ${{ secrets.WWW_COMPILERSAYSNO_ED25519 }}
          known_hosts: |
            compilersaysno.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBcmoXL+7mnXhbeJa7jraKXz1XWIYov6N+085YB5UBFh

Note that we need a workaround for the inconsistency in documentation and behavior around GitHub workspace paths like ${{github.workspace}}, to ensure it is shared properly from run to uses steps. For our small site an extra copy like this is not a big issue though.


  1. Back when one used unencrypted FTP for uploading and “serverless” was still called cgi-bin/. The added nostalgia is free. ↩︎

  2. I am convinced that there are more static site generators out there than “hello, world” applications. Pick one of over 351 options, although avoid anything involving JavaScript if you value your sanity. ↩︎

  3. I’d like to consider using SourceHut as well, once I get around to it. ↩︎