Hugo on Kubernetes & NGINX

i decided to make a website. a static one. this one. with Hugo. this is basically as a vanity project so i have some stuff to host in a Kubernetes cluster i’m running. the k8s cluster is also as a vanity project.

because i don’t like software, i wanted a way to deploy my site that doesn’t involve much of it. this post is about that.

Getting Started

i built my site by following the straight-forward Getting Started guide in the Hugo documentation.

i did hugo new site estradiol.cloud. and then cd estradiol.cloud; git init. and then i picked a ridiculous theme “inspired by terminal ricing aesthetics”, installing it like git submodule add https://github.com/joeroe/risotto.git themes/risotto; echo "theme = 'risotto'" >> hugo.toml. i appreciate the culinary naming choice.

at this point, my website is basically finished (i also changed the title in hugo.toml). i probably won’t be putting anything on it, so there’s no point fiddling with other details.

about deployment, the guide’s Basic Usage page has this to offer:

Most of our users deploy their sites using a CI/CD workflow, where a push1 to their GitHub or GitLab repository triggers a build and deployment. Popular providers include AWS Amplify, CloudCannon, Cloudflare Pages, GitHub Pages, GitLab Pages, and Netlify.

  1. The Git repository contains the entire project directory, typically excluding the public directory because the site is built after the push.

importantly, you can’t make a post about deploying this way. everyone deploys this way. if i deploy this way, this site will have no content.

it also involves some system somewhere that can run Hugo to build the site and push it to some remote system where my cluster can reach the public/ output. i definitely already need Hugo installed on my workstation if i’m going to post anything here (unlikely), so now i’m running Hugo in two places. there’s surely going to be other complex nonsense like webhooks involved.


and hang on. let’s look at this again:

  1. The Git repository contains the entire project directory, typically excluding the public directory because the site is built after the push.

you’re telling me i’m going to build a nice static site and not check the actual content into version control? couldn’t be me.

Getting Static

what if instead i pushed my site to a git repository exactly as i intend to serve it? then i could shell into my web server, pull the site, and nifty-galifty! isn’t this the way it has always been done?

one problem is that i don’t have a web server, i have a container orchestration system. there are several upsides to this (few of which are relevant for my project) but it also means that somehow my content needs to end up in a container, and i don’t want that container to need to retain state across restarts or replicas.

i could run a little pipeline that builds a container wrapping my static site, pushes it to a registry somewhere my deployments can pull it. all ready to go. but now i’ve got software again: build stages and webhooks and to make matters worse, now i’m hosting and versioning container images.

i don’t want any of this.


instead, i’d like to deploy a popular stock container from a public registry and deliver my content to it continuously.

as a minimal version of this, i could do:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  namespace: ec
  labels:
    app.kubernetes.io/instance: estradiol-cloud
    app.kubernetes.io/name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.25.4
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: /app
      name: staticsite
  - name: git-pull
    image: bitnami/git
    command:
    - /bin/bash
    - -ec
    - |
      while true; do
        cd /app && git -c safe.directory=/app pull origin trunk
        sleep 60
      done      
    volumeMounts:
    - mountPath: /app
      name: staticsite
  initContainers:
  - name: git-clone
    image: bitnami/git
    command:
    - /bin/bash
    - -ec
    - |
      git clone https://code.estradiol.cloud/tamsin/estradiol.cloud.git --no-checkout --branch trunk /tmp/app
      cd /tmp/app && git sparse-checkout init --cone
      git sparse-checkout set public && git checkout
      rm -rf /app && mv /tmp/app /app      
    volumeMounts:
    - mountPath: /app
      name: staticsite
  volumes:
  - emptyDir: {}
    name: staticsite

---
apiVersion: v1
kind: ConfigMap
metadata:
  labels:
    app.kubernetes.io/instance: estradiol-cloud
    app.kubernetes.io/name: nginx
  name: nginx-server-block
  namespace: ec
data:
  server-block.conf: |-
    server {
      listen 8080;
      root /app/public;
      index index.html;
    }    

Getting Flux’d

apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: bitnami
  namespace: default
spec:
  url: https://charts.bitnami.com/bitnami
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: bitnami
  namespace: default
spec:
  url: https://charts.bitnami.com/bitnami

Making vanity projects easy since 2024


2024-02-28