Skip to content

ADR-016: Documentation Hosting via MkDocs, GitHub Pages, and In-Cluster Nginx

Status

Accepted

Date

2026-03-21

Context

The repository contains a growing set of Markdown documentation under docs/ covering ADRs, operational runbooks, architecture overviews, and contributing guides. This content is only readable by navigating the raw GitHub file tree. There is no rendered, searchable, or hyperlinked view of the docs accessible to users of the project.

Two access patterns are desirable:

  1. Public/remote — a rendered site accessible without cloning the repo, linked from the README and GitHub repository page.
  2. In-cluster/local — accessible from within the local Minikube environment alongside other cluster services (e.g. grafana.local, keycloak.local), consistent with the cluster-first philosophy of the project.

The repo already has mkdocs.yml configured with the Material theme and docs/requirements.txt pinning the pip dependencies. The authoring toolchain is therefore already decided; only the hosting mechanism is open.

Decision

Authoring: MkDocs + Material theme (existing)

No change. mkdocs.yml remains the single source of truth for site structure. docs/requirements.txt is the canonical pip dependency file for all doc builds.

Public hosting: GitHub Pages via GitHub Actions

A workflow at .github/workflows/docs.yml triggers on pushes to main that touch docs/**, mkdocs.yml, docs/requirements.txt, or the docs Dockerfile/nginx config. It:

  1. Installs pip dependencies from docs/requirements.txt.
  2. Runs mkdocs build --strict to catch broken links.
  3. Uploads the built site/ directory as a GitHub Pages artifact.
  4. Deploys it via actions/deploy-pages.

In parallel, the same workflow builds a Docker image (multi-stage: Python/MkDocs builder → nginx:alpine) and pushes it to ghcr.io/marcspeckmann/homekube-docs tagged with both latest and the short Git SHA.

In-cluster hosting: nginx Deployment at https://docs.local

A Kubernetes Deployment in the docs namespace runs the ghcr.io/marcspeckmann/homekube-docs image. The image is built from apps/docs/Dockerfile and serves the pre-built static site via nginx.

Resources added:

apps/docs/
  Dockerfile              # multi-stage: MkDocs build → nginx:alpine
  nginx.conf              # serve MkDocs directory URLs; /healthz health endpoint
  namespace.yaml          # docs namespace
  deployment.yaml         # Deployment + Service + Ingress (docs.local + TLS)
  network-policies.yaml   # default-deny + allow ingress-nginx + allow DNS egress
  kustomization.yaml      # Kustomize manifest list
clusters/local/apps.yaml  # Flux Kustomization object (depends on infrastructure)

Ingress: https://docs.local with cert-manager TLS (local-ca-issuer), consistent with existing services.

Image management: The Deployment manifest carries a Flux image-policy annotation (# {"$imagepolicy": "flux-system:homekube-docs"}), ready for image automation (ADR-015) once a corresponding ImageRepository and ImagePolicy are configured.

Alternatives Considered

Option Reason Not Chosen
GitHub Pages only (no in-cluster) Breaks parity with other cluster services; no local-only access
Read the Docs External service dependency; overkill for a personal sandbox
Serve docs directly from Git clone (init container) Requires git and pip in the running container; larger image and slower startup
ConfigMap-mounted HTML Impractical for a full MkDocs site (~100+ files); ConfigMap size limits apply
Separate docs repository Fragments the monorepo; ADR-003 explicitly prefers a single repo

Consequences

Positive

  • Documentation is rendered and searchable at both a public URL and https://docs.local
  • mkdocs build --strict in CI catches broken internal links before merge
  • Image is tiny (nginx:alpine + static HTML — typically < 50 MB)
  • Consistent with existing ingress + TLS pattern used by Grafana and Keycloak
  • GitHub Pages URL is automatically linked from the GitHub repository page

Negative

  • CI must rebuild and push the Docker image on every qualifying push; adds ~2 min to the pipeline
  • ghcr.io image requires a GitHub token for the cluster to pull (or the image must be public)
  • Image automation setup (ADR-015 ImageRepository / ImagePolicy) is a follow-up step before the in-cluster deployment auto-updates on new pushes

Setup Checklist

  1. Enable GitHub Pages in repository Settings → Pages → Source: GitHub Actions.
  2. Ensure the homekube-docs image on ghcr.io is set to public (or create an imagePullSecret in the docs namespace).
  3. Add docs.local to /etc/hosts: echo "$(minikube ip) docs.local" | sudo tee -a /etc/hosts.
  4. After Flux reconciles apps, verify: kubectl get pods,ingress -n docs.
  5. Open https://docs.local (accept self-signed cert from local CA).