user@homelab:~/docs$ cat decisions.md

Architecture decisions

ADR log: the key design choices, their context and consequences

# contents

Each record states the context, the decision and its consequences. Records are not edited after acceptance; a changed decision gets a new record that supersedes the old one.

ADRDecisionStatus
001Bare-metal k3s on the laptopAccepted
002Ubuntu Server without a desktopAccepted
003k3s as the Kubernetes distributionAccepted
004Cloudflare Tunnel for public exposureAccepted
005Tailscale for administrative accessAccepted
006Cluster administration on the server onlyAccepted
007GitOps with Flux (pull model)Accepted
008Private repository and private image registryAccepted
009Static documentation site with HugoAccepted

ADR-001: Bare-metal k3s on the laptop

Context. The only hardware is a laptop with 8 GB RAM. Options: WSL2 + kind on Windows, a VM on Windows, or Linux directly on the hardware.

Decision. Install Ubuntu Server alongside Windows (dual boot) and run k3s directly on it.

Consequences.

  • ✅ all memory available to workloads, 24/7 operation, stable LAN address
  • ✅ Windows remains available when needed
  • ❌ single node, single point of failure; recovery relies on Git and backups

ADR-002: Ubuntu Server without a desktop

Context. A desktop environment costs 1.5–2 GB of RAM. The node is managed remotely.

Decision. Ubuntu Server LTS, no GUI, administration over SSH only.

Consequences.

  • ✅ more memory for the cluster, smaller attack surface
  • ❌ every task is done in a terminal

ADR-003: k3s as the Kubernetes distribution

Context. The cluster must run on one 8 GB node and match the CKAD exam version (Kubernetes v1.35).

Decision. k3s, pinned to the v1.35 channel.

Consequences.

  • ✅ single binary with containerd, Flannel, CoreDNS, Traefik and local-path storage included
  • ✅ certified Kubernetes, same API as the exam
  • ❌ cluster administration differs from kubeadm; CKA practice needs a separate kubeadm cluster in VMs

ADR-004: Cloudflare Tunnel for public exposure

Context. The site must be public. The node is behind double NAT on Wi-Fi without a guaranteed public IPv4. Port forwarding would expose the home network to scanning.

Decision. A cloudflared connector in the cluster opens an outbound tunnel to Cloudflare; only HTTP to Traefik is published.

Consequences.

  • ✅ no inbound ports, HTTPS and DDoS protection by Cloudflare, own domain
  • ❌ public traffic depends on Cloudflare; the tunnel token is a credential to protect

ADR-005: Tailscale for administrative access

Context. SSH must work from the Mac and iPad outside the home network. Alternatives: port forwarding, Cloudflare Access/WARP, Tailscale.

Decision. Tailscale on the server, Mac and iPad. OpenSSH with keys stays the authentication layer; Tailscale SSH is not used.

Consequences.

  • ✅ no inbound ports, the server is invisible from the internet, works with Termius unchanged
  • ✅ independent of Cloudflare, so the node stays reachable if the public path fails
  • ❌ requires the Tailscale client on every admin device; may clash with other VPN clients

ADR-006: Cluster administration on the server only

Context. The Mac also holds kubeconfigs for other clusters. A homelab admin certificate on the workstation adds risk of misdirected commands.

Decision. kubectl and k9s run on the server over SSH. No homelab kubeconfig on the Mac.

Consequences.

  • ✅ the admin certificate never leaves the node, no context mix-ups
  • ✅ same working style as the CKAD exam (terminal on a remote machine)
  • ❌ GUI tools on the Mac (Lens, IDE plugins) are not available

ADR-007: GitOps with Flux (pull model)

Context. Deployments should be automatic after git push. GitHub cannot reach the cluster, and opening access to it would contradict ADR-004 and ADR-005.

Decision. GitHub Actions builds and publishes images; Flux in the cluster pulls the repository and applies it. Git is the single source of truth.

Consequences.

  • ✅ no credentials to the cluster outside it; Flux uses a read-only deploy key
  • ✅ drift is reverted automatically; rollback is git revert
  • ❌ manual changes in the cluster must be done with Flux suspended, or they are reverted
  • ❌ ~1.5–3 minutes from push to live

ADR-008: Private repository and private image registry

Context. The repository holds infrastructure configuration. The image contains only public content, but should not be publicly listed.

Decision. Private GitHub repository and private GHCR package; the cluster pulls with a read:packages token stored as the ghcr-pull Secret.

Consequences.

  • ✅ configuration and images visible to the owner only
  • ❌ a token in the cluster that expires yearly and must be renewed

ADR-009: Static documentation site with Hugo

Context. The documentation should be versioned with the infrastructure, public, bilingual and cheap to host on the node itself.

Decision. Hugo with a custom theme (no external themes or CDNs), served by non-root nginx, Czech and English content side by side.

Consequences.

  • ✅ fast, static, nothing executed server-side; strict CSP possible
  • ✅ content is Markdown in the same repository as the cluster configuration
  • ❌ the theme is maintained in-house