Using BSSG, BusyBox, and Kubernetes to Host and Update Static Websites

Using BusyBox to Host and Update Static Websites on Kubernetes
Prerequisites:
- A functioning Kubernetes setup
- A BSSG site running locally on your computer
- A GitHub repo created that contains your BSSG output. Mine is at https://github.com/llamallama/blog
If you haven't heard of it, BSSG is a new and very capable static site generator written entirely in Bash. It support posts, pages, tags, themes, and more! This blog was built with it and I decided to host it from my home lab. My lab runs a flavor of Kubernetes called k3s. Since this blog is static, I wanted to find the smallest tool set I could use to host it.
That was when I learned that busybox has a all the tools required to do just that! Per BusyBox's site:
BusyBox combines tiny versions of many common UNIX utilities into a single
small executable. It provides replacements for most of the utilities you
usually find in GNU fileutils, shellutils, etc. The utilities in BusyBox
generally have fewer options than their full-featured GNU cousins; however,
the options that are included provide the expected functionality and behave
very much like their GNU counterparts. BusyBox provides a fairly complete
environment for any small or embedded system.
Note that it says embedded systems, so this is probably not the most production ready or most performant way to do host a static site, but that is what puts the "lab" in "home lab". Experimentation!
Not only can it host the site, but it can also download the code from GitHub. The way this is done will be via three of BusyBox's many utilities: http, wget, and unzip.
Enough preamble, here's the setup.
Kubernetes
Set up a standard k8s deployment and use this for the container spec, adjusting to suit your GitHub and k8s setup
spec:
containers:
- name: blog
# Launch busybox httpd
command: [httpd]
# Run httpd as the www-data user, in the foreground, in verbose mode, on port 8000
args: [ -u, www-data, -f, -v, -p, "8000"]
image: busybox
workingDir: /var/www
ports:
- containerPort: 8000
name: tcp8000
protocol: TCP
lifecycle:
postStart:
exec:
command:
- sh
- -c
- |
mkdir -p /usr/local/bin;
'#!/bin/sh';
set -e;
wget https://github.com/llamallama/blog/archive/refs/heads/main.zip -O /tmp/main.zip;
rm -rf /var/www/*;
unzip /tmp/main.zip;
mv blog-main/* ./;
rm -rf blog-main /tmp/main.zip;
In this example we are using the official busybox image and setting the workingDir to /var/www
and having BusyBox's httpd serve the static content in /var/www. The real magic happens in the postStart lifecycle hook. It uses BusyBox's wget to download the main GitHub branch as a zip file, unzip it, and move the files to /var/www.
Obviously there are some part missing here. Service and ingress settings chief among them, but those are outside of the scope of this post. The relevant parts are the use of BusyBox and the postStart hook.
Deploy from BSSG
In your BSSG scripts/
folder, create a file called deploy.sh
and make it look like this, adjusting it to fit your setup.
#!/usr/bin/env bash
set -eu -o pipefail
# Set our dirs
output_dir=~/personal/blog/output
git_dir=~/personal/blog.git
# Clear the Git dir and copy the BSSG output to it
rm -rf "$git_dir"/*
cp -R "$output_dir"/* "$git_dir"/
# Add, commit, and push to GitHub
cd "$git_dir"
git add .
git commit -am "Update $(date)"
git push origin main
# Redeploy the container
kubectl rollout restart deployment/blog
On my setup ~/personal/blog/output
contains my BSSG output. ~/personal/blog.git
is my Git repo working copy which started empty. The above script copies our blog output to our Git working copy, commits, pushes, and then uses kubectl to redeploy the pod, which will run the postStart lifecycle hook again, which will get the latest site content.
Next I run
./bssg.sh build --deploy --site-url "https://blog.pipetogrep.org"
Conclusion
As you can see, it really takes very little to host a static website. In this case, Bash is our "CMS" and BusyBox is our web server. No JavaScript. No Apache or Nginx. Just simple (except k8s) tools to do a simple job quickly.
Why did I do it like this? Because I can and I wanted to!