Record the operator's 2026-11-08 decision to move public HTTP(S) ingress from edge port-forwarding of 80/443 to an outbound-only Cloudflare Tunnel (in-cluster cloudflared, proxied CNAMEs via ExternalDNS). Motivation: the GFiber router cannot hairpin NAT and advertises Google IPv6 DNS via RA, so LAN clients bypass Pi-hole split horizon and fail. Docs 00/01/03/07/08/09/13/14, GLOSSARY and definition-of-done brought into line. R-DNS-01..03 and R-TLS-01..03 wording unchanged. New requirements: R-DNS-04 (no 80/443 forward, resolver-independent LAN reachability), R-DNS-05 (only `public` services at the tunnel; 404 catch-all; no public record for `internal`), R-SEC-04 (Kubernetes API never publicly reachable). Co-Authored-By: Claude Opus 5.5 <[email protected]>
Architecture Decision Records (ADRs)
ADRs are an append-only ledger of the significant technology and pattern choices for this platform. Each records the context, the decision, the rejected alternatives, and the requirements it serves. Do not edit an accepted decision in place — supersede it with a new ADR.
ADRs are specifications and are immutable to AI agents (R-TEST-06). Agents may draft a proposed ADR and escalate it, but only the human operator/architect approves/accepts or edits ADRs. See
../AGENTS.md§3 and §11.
Process (read before proposing a change)
- To change a decision, create a new ADR with the next number and set
Status: Proposed,Supersedes: ADR-XXXX. (Agents draft + escalate; the human approves.) - State what new information invalidates the old decision.
- On approval, set the new ADR
Acceptedand the old oneSuperseded by: ADR-YYYY. - Never silently re-interpret an accepted ADR (see
../AGENTS.md§3).
Template
# ADR-XXXX — <title>
- Status: Proposed | Accepted | Superseded by ADR-YYYY
- Date: <YYYY-MM-DD>
- Serves: <requirement IDs, e.g. R-NET-01, R-NET-06>
- Supersedes: <ADR-ZZZZ or —>
## Context
<forces, constraints, why a decision is needed>
## Decision
<what we chose, stated decisively>
## Alternatives considered
<options and why rejected>
## Consequences
<positive, negative, follow-ups, risks>
Index
| ADR | Title | Status | Serves |
|---|---|---|---|
| 0001 | Kubernetes distribution: k3s | Accepted | R-PLAT-01 |
| 0002 | CNI & network policy & ingress: Cilium + Gateway API | Accepted | R-NET-01..07, R-DNS-01 |
| 0003 | Distributed storage: Longhorn (+ bulk tier, Velero) | Accepted | R-STATE-01..04 |
| 0004 | GitOps engine: Flux CD | Accepted | R-GITOPS-01..03 |
| 0005 | VM workloads: KubeVirt + CDI | Accepted | R-PLAT-03 |
| 0006 | TLS: cert-manager + Let's Encrypt (DNS-01) | Accepted | R-TLS-01..03 |
| 0007 | Secrets: SOPS + age | Accepted | R-SEC-01 |
| 0008 | VPN egress model: Gluetun sidecar + Cilium Egress Gateway | R-NET-05/06 | |
| 0009 | Automated updates: Flux image automation + Renovate | Accepted | R-UPD-01..03 |
| 0010 | Conformance testing: automated, reproducible, behavior-based | Accepted | R-TEST-01..06 |
| 0011 | Conformance test tooling: Kyverno + Chainsaw | Accepted | R-TEST-01..05 |
| 0012 | VPN egress: single shared in-cluster ExpressVPN gateway | R-NET-05/06/07 | |
| 0013 | VPN provider: PIA over OpenVPN-UDP + port forwarding | Accepted — provider/protocol only (client+steering superseded by 0015) | R-NET-05/06/07 |
| 0014 | Identity Provider: Keycloak (SSO via OIDC + forward-auth) | Accepted | R-IDP-01..06 |
| 0015 | VPN egress steering: node-level tunnel + Cilium Egress Gateway (host kill-switch) | Accepted | R-NET-05/06/07 |
| 0016 | Public ingress: Cloudflare Tunnel (outbound-only) instead of edge port-forwarding | Proposed | R-DNS-01..05, R-TLS-01/02, R-SEC-02/04 |