<p>i decided to make a website. a static one. this one. with <ahref="https://gohugo.io">Hugo</a>. the
main reason i have for needing a website is as a vanity project, so i have some
stuff to host in a <ahref="https://kubernetes.io">Kubernetes</a> cluster i’m running. the k8s cluster is
also a vanity project.</p>
<p>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.</p>
<h2id="getting-started">Getting Started</h2>
<p>i built my site by following the straight-forward
<em><ahref="https://gohugo.io/getting-started">Getting Started</a></em> guide in the Hugo documentation.</p>
<p>i did <code>hugo new site estradiol.cloud</code>. and then <code>cd estradiol.cloud; git init</code>.
and then i picked a ridiculous theme
<ahref="https://github.com/joeroe/risotto">“inspired by terminal ricing aesthetics”</a>, installing it like <code>git submodule add https://github.com/joeroe/risotto.git themes/risotto; echo "theme = 'risotto'">> hugo.toml</code>.<supid="fnref:1"><ahref="#fn:1"class="footnote-ref"role="doc-noteref">1</a></sup></p>
<p>at this point, my website is basically finished (i also changed the title in
<code>hugo.toml</code>). i probably won’t be putting anything on it, so there’s no point
fiddling with other details.</p>
<p>about deployment, the Hugo guide’s <em><ahref="https://gohugo.io/getting-started/usage/#deploy-your-site">Basic Usage</a></em> page has this
to offer:</p>
<blockquote>
<p>Most of our users deploy their sites using a CI/CD workflow, where a push<sup>1</sup>
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.</p>
<ol>
<li>The Git repository contains the entire project directory, typically excluding the
public directory because the site is built <em>after</em> the push.</li>
</ol>
</blockquote>
<p>importantly, you can’t make a post about deploying this way. <em>everyone</em> deploys
this way. if <em>i</em> deploy this way, this site will have no content.</p>
<p>this approach also involves a build system somewhere that can run Hugo to
compile the code and assets and push them onto my host. i definitely
already need Hugo installed on my laptop if i’m going to post anything.<supid="fnref:2"><ahref="#fn:2"class="footnote-ref"role="doc-noteref">2</a></sup>
so now i’m running Hugo in two places. there’s surely going to be other
complex nonsense like webhooks involved.</p>
<p><imgsrc="images/hugo-github-pages.svg"alt="diagram: deploy w/ GitHub Pages & Actions"></p>
<hr>
<p>and hang on. let’s look at this again:</p>
<blockquote>
<ol>
<li>The Git repository contains the entire project directory, typically excluding the
public directory because the site is built <em>after</em> the push.</li>
</ol>
</blockquote>
<p>you’re telling me i’m going to build a nice static site and not check the
<em>actual content</em> into version control? couldn’t be me.</p>
<h2id="getting-static">Getting Static</h2>
<p>suppose i instead check my content in exactly as i intend to serve it?
then i could shell into my server box, pull the site, and <em>nifty-galifty!</em> isn’t
this the way it has <ahref="https://www.mikecurato.com/worm-loves-worm">always been done</a>?</p>
<p>my problem is that i don’t have a server box. i have a <em>container orchestration
system</em>. there are several upsides to this<supid="fnref:3"><ahref="#fn:3"class="footnote-ref"role="doc-noteref">3</a></sup> but it means that <em>somehow</em> my
generated content needs to end up in a container. because <ahref="https://kubernetes.io/docs/concepts/workloads/pods/">Pods</a> are
ephemeral and i’d like to run my site with horizontal scalability<supid="fnref:4"><ahref="#fn:4"class="footnote-ref"role="doc-noteref">4</a></sup>, i don’t
want my container to retain runtime state across restarts or replicas.</p>
<p>i <em>could</em> run a little pipeline that builds a container image wrapping my
content and pushes it to a registry. when i deploy, the cluster pulls the
image, content and all. all ready to go. but now i’ve got <em>software</em> again:
build stages and webhooks and, to make matters worse, now i’m hosting
<p>when a new <code>Pod</code> comes up, the <code>initContainer</code> mounts the
<ahref="https://kubernetes.io/docs/concepts/storage/volumes/#emptydir"><code>emptyDir</code></a> at <code>/www</code> and clones the repository into it. i use
<code>git sparse-checkout</code> to avoid pulling repository contents i don’t want to serve
</span></span></code></pre></div><p>and i create a <ahref="https://kubernetes.io/docs/concepts/configuration/configmap/">ConfigMap</a> with a server block to configure
<code>nginx</code> to use Hugo’s <code>public/</code> as root:</p>
<p>my <code>git pull</code> control loop takes things over from here and i’m on easy street.</p>
<h2id="getting-web">Getting Web</h2>
<p>this is going great! my <code>Pod</code> is running. it’s serving out my code. i get
Continuous Deployment™ for the low price of 11 lines <code>bash</code>. i mean…
no one can actually browse to my website<supid="fnref:6"><ahref="#fn:6"class="footnote-ref"role="doc-noteref">6</a></sup> but that will be an easy fix,
right? yes. networking is always the easy part.</p>
<p>first, i need a <ahref="https://kubernetes.io/docs/concepts/services-networking/service/"><code>Service</code></a>. this gives me a proxy to my several
replicas<supid="fnref:7"><ahref="#fn:7"class="footnote-ref"role="doc-noteref">7</a></sup> and in-cluster service discovery.</p>
<p>next, i need an <ahref="https://kubernetes.io/docs/concepts/services-networking/ingress/"><code>Ingress</code></a> to handle traffic inbound to the cluster
in my <code>Ingress</code> resource(s). the controller also also creates a <code>Service</code> of
type <code>LoadBalancer</code>, which <ahref="https://docs.digitalocean.com/products/kubernetes/how-to/add-load-balancers/">magically</a> creates a load balancer appliance
in my cloud provider. off-screen, i can point DNS to <em>that</em> appliance to finish
<p>you can tell it’s working by looking at your browser bar.</p>
<hr>
<p>as this has come together, i’ve gotten increasingly anxious about how much
YAML i’ve had to write. this is a problem because YAML is software and, as
established, i’m hoping not to have much of that. it’s also annoying that most
of this YAML really is just boilerplate.</p>
<p>conveniently, <ahref="https://bitnami.com/">Bitnami</a> maintains a <ahref="https://helm.sh">Helm</a> Chart that templates
out all the boilerplate and does exactly what i’ve just been doing.<supid="fnref:8"><ahref="#fn:8"class="footnote-ref"role="doc-noteref">8</a></sup> i can
replace all my YAML with a call out to this chart and a few lines of
configuration, assuming i have <ahref="https://helm.sh/docs/intro/install/">helm client installed</a>:</p>
<p>configuration for the <code>git-clone</code> script and our custom server block are added
via <code>values.yaml</code>. the <code>git-pull</code> loop configured by the chart works as-is.
by using the chart, we get a few other nicities. for instance,
my <code>Pod</code>s are now managed by a <ahref="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/"><code>Deployment</code></a>.<supid="fnref:9"><ahref="#fn:9"class="footnote-ref"role="doc-noteref">9</a></sup> this will make my
grand scale-out plans a breeze.</p>
<h2id="getting-fluxd">Getting Flux’d</h2>
<p>by now, i’m riding high. my whole setup is my static site code and <30 lines of
YAML.</p>
<p>i <em>do</em> have a bunch of stuff deployed into my cluster, and none of this is very
reproducible without all of that. my workflow has also expanded to:</p>
<p>when i push these to my <code>flux</code><ahref="https://gitlab.com/no_reply/sublingual/-/tree/trunk/estradiol.cloud">source repository</a>, the Helm
<h2id="a-note-about-software">A Note About Software</h2>
<p>in the end, i’m forced to admit there’s still a lot of software involved in all
of this. setting aside the stuff that provisions and scales my cluster nodes,
and the <em>magic</em><code>LoadBalancer</code>, i have:</p>
<ul>
<li><code>nginx</code> (running from a stock image);</li>
<li><code>git</code>&<code>bash</code> (running from a stock image);</li>
<li>a remote git server (i’m running <code>gitea</code><supid="fnref:10"><ahref="#fn:10"class="footnote-ref"role="doc-noteref">10</a></sup>, but github dot com is fine here);</li>
<li>Kubernetes (oops!);
<ul>
<li><code>flux</code>, especially <code>kustomize-controller</code> and <code>helm-controller</code>;</li>
<li><code>ingress-nginx</code> controller;</li>
<li><code>cert-manager</code> and Let’s Encrypt;</li>
<p>the bulk of this i’ll be able to reuse for the other things i deploy on the
cluster<supid="fnref:11"><ahref="#fn:11"class="footnote-ref"role="doc-noteref">11</a></sup>. and it replaces SASS black-boxes like “AWS Amplify, CloudCannon,
Cloudflare Pages, GitHub Pages, GitLab Pages, and Netlify” in the recommended
Hugo deployment.</p>
<p>to actually deploy my site, i get to maintain a <code>bash</code> scripts for <code>git-clone</code>, my
NGINX config, and a couple of blobs of YAML.</p>
<p>at least there are no webhooks.</p>
<hr>
<p><em>fin</em></p>
<divclass="footnotes"role="doc-endnotes">
<hr>
<ol>
<liid="fn:1">
<p>i appreciate the culinary branding. <ahref="#fnref:1"class="footnote-backref"role="doc-backlink">↩︎</a></p>
<p>few of which could be considered relevant for my project. <ahref="#fnref:3"class="footnote-backref"role="doc-backlink">↩︎</a></p>
</li>
<liid="fn:4">
<p>i absolutely will not need this <ahref="#fnref:4"class="footnote-backref"role="doc-backlink">↩︎</a></p>
</li>
<liid="fn:5">
<p>i added <code>disableHTML = true</code> and <code>disableXML = true</code> to <code>[minify]</code>
configuration in <code>hugo.toml</code> to keep HTML and RSS diffs readable. <ahref="#fnref:5"class="footnote-backref"role="doc-backlink">↩︎</a> <ahref="#fnref1:5"class="footnote-backref"role="doc-backlink">↩︎</a></p>
</li>
<liid="fn:6">
<p>i can check that its working, at least, with a <ahref="https://kubernetes.io/docs/tasks/access-application-cluster/port-forward-access-application-cluster/">port-forward</a>. <ahref="#fnref:6"class="footnote-backref"role="doc-backlink">↩︎</a></p>
<p>what incredible luck! (obviously, until now i’ve been working backward from this chart) <ahref="#fnref:8"class="footnote-backref"role="doc-backlink">↩︎</a></p>
</li>
<liid="fn:9">
<p>i also snuck a TLS certificate configuration via Let’s Encrypt with
<ahref="https://cert-manager.io/docs/tutorials/acme/nginx-ingress/"><code>cert-manager</code></a> into this iteration. if you’re following along at home and don’t have <code>cert-manager</code> installed, this should still work fine (but with no HTTPs). <ahref="#fnref:9"class="footnote-backref"role="doc-backlink">↩︎</a></p>
</li>
<liid="fn:10">
<p>because i’m running <code>gitea</code> in my cluster and i want to avoid a circular
dependency for my <code>flux</code> source repository, i also depend on GitLab dot com. <ahref="#fnref:10"class="footnote-backref"role="doc-backlink">↩︎</a></p>
<p><ahref="https://doi.org/10.5749/minnesota/9780816650477.003.0001">the regrown limb can be monstrous, duplicated, potent. We have all been injured, profoundly.</a></p>