+++ title = 'Hugo on Kubernetes & NGINX' date = 2024-02-28T15:35:46-08:00 draft = true +++ i decided to make a website. a static one. this one. with [Hugo][hugo]. this is basically as a vanity project so i have some stuff to host in a [Kubernetes][k8s] 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. this post is about that. ## Getting Started i built my site by following the straight-forward _[Getting Started][hugo-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"][risotto], 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 much on it, so there's no point fussing with other details. about deployment, the guide's _[Basic Usage][hugo-deploy]_ page has this to offer: > Most of our users deploy their sites using a CI/CD workflow, where a push{{< sup "1" >}} > 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 depoying this way. _everyone_ deploys this way. 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 it. 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 definitely 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 webserver, pull the site, and _nifty-galifty!_ isn't this the way it has [always been done][worm]? one problem is that i don't have a webserver, i have a _container orchestration system_. there are several upsides to this (few of which are relevant for my project) but it demands i get a bit more clever. i _could_ run a little pipeline that builds a container wrapping my static site, pushes it to a registry somewhere so my deployments can pull it, all ready to go. but now i've got _software_ again; build stages and webhooks and i'm hosting and versioning container images. i don't want any of this. ```yaml apiVersion: apps/v1 kind: Deployment metadata: labels: app.kubernetes.io/instance: estradiol-cloud app.kubernetes.io/name: nginx name: web-nginx namespace: estradiol-cloud spec: replicas: 1 selector: matchLabels: app.kubernetes.io/instance: estradiol-cloud app.kubernetes.io/name: nginx template: metadata: labels: app.kubernetes.io/instance: estradiol-cloud app.kubernetes.io/name: nginx spec: containers: - name: git-repo-syncer image: docker.io/bitnami/git:2.43.2-debian-12-r2 imagePullPolicy: IfNotPresent 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 - name: nginx image: docker.io/bitnami/nginx:1.25.4-debian-12-r2 imagePullPolicy: IfNotPresent env: - name: NGINX_HTTP_PORT_NUMBER value: "8080" livenessProbe: tcpSocket: port: http readinessProbe: tcpSocket: port: http ports: - containerPort: 8080 name: http protocol: TCP volumeMounts: - mountPath: /opt/bitnami/nginx/conf/server_blocks name: nginx-server-block - mountPath: /app name: staticsite initContainers: - name: git-clone-repository image: docker.io/bitnami/git:2.43.2-debian-12-r2 imagePullPolicy: IfNotPresent command: - /bin/bash - -ec - | [[ -f "/opt/bitnami/scripts/git/entrypoint.sh" ]] && source "/opt/bitnami/scripts/git/entrypoint.sh" git clone https://code.estradiol.cloud/tamsin/estradiol.cloud.git --no-checkout --branch trunk /tmp/app [[ "$?" -eq 0 ]] && cd /tmp/app && git sparse-checkout init --cone && git sparse-checkout set public && git checkout && shopt -s dotglob && rm -rf /app/* && mv /tmp/app/* /app/ volumeMounts: - mountPath: /app name: staticsite restartPolicy: Always terminationGracePeriodSeconds: 30 volumes: - configMap: defaultMode: 420 name: web-nginx-server-block name: nginx-server-block - emptyDir: {} name: staticsite ``` ## Getting Flux'd ```yaml apiVersion: source.toolkit.fluxcd.io/v1beta2 kind: HelmRepository metadata: name: bitnami namespace: default spec: url: https://charts.bitnami.com/bitnami ``` ```yaml apiVersion: source.toolkit.fluxcd.io/v1beta2 kind: HelmRepository metadata: name: bitnami namespace: default spec: url: https://charts.bitnami.com/bitnami ``` [hugo]: https://gohugo.io [hugo-deploy]: https://gohugo.io/getting-started/usage/#deploy-your-site [hugo-started]: https://gohugo.io/getting-started [k8s]: https://kubernetes.io [risotto]: https://github.com/joeroe/risotto [worm]: https://www.mikecurato.com/worm-loves-worm