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.
| ADR | Decision | Status |
|---|---|---|
| 001 | Bare-metal k3s on the laptop | Accepted |
| 002 | Ubuntu Server without a desktop | Accepted |
| 003 | k3s as the Kubernetes distribution | Accepted |
| 004 | Cloudflare Tunnel for public exposure | Accepted |
| 005 | Tailscale for administrative access | Accepted |
| 006 | Cluster administration on the server only | Accepted |
| 007 | GitOps with Flux (pull model) | Accepted |
| 008 | Private repository and private image registry | Accepted |
| 009 | Static documentation site with Hugo | Accepted |
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