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.
-
Back when one used unencrypted FTP for uploading and “serverless” was still called
cgi-bin/
. The added nostalgia is free. ↩︎ -
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. ↩︎
-
I’d like to consider using SourceHut as well, once I get around to it. ↩︎