Why Self-Host a Static Site on Kubernetes?

If you’re already running a homelab or production Kubernetes cluster, deploying a static site there makes sense. You get full control over your infrastructure, TLS via cert-manager, and it’s a great learning exercise.

In this post, I’ll walk through how I deploy this very site — built with Hugo and the PaperMod theme — to my K8s cluster.

The Stack

Component Tool
Static Site Gen Hugo
Theme PaperMod
Container Nginx Alpine
Orchestration Kubernetes
Deployment ArgoCD / Helm
TLS cert-manager
DNS Cloudflare

Building the Container Image

The multi-stage Dockerfile keeps things lean:

FROM hugomods/hugo:exts as builder
WORKDIR /src
COPY . .
RUN hugo --minify

FROM nginx:alpine
COPY --from=builder /src/public /usr/share/nginx/html
EXPOSE 80

Build and push:

docker build -t registry.example.com/mikefrantum-dev:latest .
docker push registry.example.com/mikefrantum-dev:latest

Kubernetes Manifests

Here’s a basic deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mikefrantum-dev
  namespace: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: mikefrantum-dev
  template:
    metadata:
      labels:
        app: mikefrantum-dev
    spec:
      containers:
        - name: hugo
          image: registry.example.com/mikefrantum-dev:latest
          ports:
            - containerPort: 80

Adding an Ingress with TLS

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mikefrantum-dev
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - mikefrantum.dev
      secretName: mikefrantum-dev-tls
  rules:
    - host: mikefrantum.dev
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: mikefrantum-dev
                port:
                  number: 80

GitOps with ArgoCD

Once your manifests are in a Git repo, ArgoCD can watch for changes and auto-sync:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: mikefrantum-dev
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/mikefrantum/mikefrantum-dev-deploy
    targetRevision: HEAD
    path: k8s
  destination:
    server: https://kubernetes.default.svc
    namespace: web
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

What’s Next

  • Add monitoring with Prometheus + Grafana
  • Set up a CI pipeline with GitHub Actions to auto-build on push
  • Explore caching strategies with Cloudflare

This is a test post for styling purposes.