user@homelab:~/docs$ cat 14-public.md

Veřejný web (Cloudflare Tunnel)

Vyčištění obsahu, výběr domény, tunel z clusteru do Cloudflare a svejklab.dev online

# obsah

Výsledek

Web je veřejně na https://svejklab.dev:

  • žádný otevřený port na routeru, žádná veřejná IP adresa a žádný port forwarding,
  • HTTPS zajišťuje Cloudflare (certifikát i obnova),
  • z clusteru se ven dostane jen HTTP přes Traefik, SSH ani Kubernetes API přes tunel nevedou.
text
návštěvník ─HTTPS─▶ Cloudflare (prg/vie) ◀─tunel, odchozí QUIC─ cloudflared (pod)
                                                                     │ HTTP
                                                                     ▼
                                                     Traefik (kube-system) ─▶ homelab-docs

Klíčové je, že spojení navazuje cloudflared z clusteru směrem ven. Router ho vidí jako běžný odchozí provoz, stejně jako když si notebook otevře web. Nevadí proto ani dvojitý NAT (router poskytovatele → vlastní router), ani chybějící veřejná IPv4.

1. Vyčištění obsahu

Než šel web ven, odstranili jsme z něj všechno, co by prozrazovalo detaily o domácí síti:

CoNahrazeno
IP adresa serveru<ip-serveru>
IP adresy routerů<ip-routeru>
uživatelské jméno na serveru<uživatel>, prompt v záhlaví user@homelab
konkrétní modely routerůobecně „router poskytovatele“ a „vlastní Wi-Fi router“
MAC adresy, SSID, hesla, tokenyna webu nikdy nebyly

Kontrola na sestaveném webu, ne jen ve zdrojácích:

shell @ mac
cd homelab-docs/site && hugo --destination /tmp/public
grep -rhoE "192\.168\.[0-9.]+" /tmp/public | sort -u    # musí vrátit nic

2. Výběr domény

Doména je koupená přímo u Cloudflare. Ten prodává za nákupní cenu bez přirážky, takže cena za prodloužení je stejná jako ceník registru.

Při výběru rozhodovala cena prodloužení, ne cena prvního roku:

Skupina koncovekCena 1. rokProdlouženíVerdikt
.bid, .win, .download, .loan…~$4~$5❌ pověst spamových koncovek, firemní filtry je často blokují
.fun, .online, .site, .store, .tech~$5$25–49❌ levný jen první rok
.fyi~$5~$5✅ nejlevnější rozumná volba
.dev~$8~$12✅ vybráno: technická koncovka, vynucuje HTTPS (HSTS preload)

Jméno svejklab = příjmení + lab. Web běží přímo na hlavní doméně a další služby dostanou subdomény: grafana.svejklab.dev, mcp.svejklab.dev…

3. Tunel v Cloudflare

Zero Trust → Networks → Tunnels → Create a tunnel:

  1. typ Cloudflared, název homelab,
  2. obrazovka Install and Run nabízí instalaci pro Windows, macOS nebo Debian. Nic z toho se nespouští, konektor poběží v Kubernetes. Ikonou kopírování se jen zkopíruje celý příkaz, protože obsahuje token,
  3. Continue se odemkne až po připojení konektoru, takže nejdřív kroky 4 a 5 níže.

4. Token do clusteru jako Secret

shell @ server
kubectl create namespace cloudflared --dry-run=client -o yaml | kubectl apply -f -

Teď samostatně jen tento řádek. Pak vlož zkopírovaný příkaz z Cloudflare (Cmd+V) a Enter:

shell @ server
read -rsp "Vlož celý příkaz z Cloudflare: " LINE; echo

Pak:

shell @ server
TOKEN="${LINE##* }"; unset LINE
echo "Délka tokenu: ${#TOKEN}"
kubectl -n cloudflared create secret generic tunnel-token --from-literal=token="$TOKEN"
unset TOKEN
  • read -rsp načte vstup bez zobrazení a bez zápisu do historie (-s = silent),
  • ${LINE##* } je bash expanze, která z textu vezme poslední slovo, tedy samotný token,
  • ${#TOKEN} vypíše jen délku tokenu (u nás 184). Tím se ověří vložení, aniž by se token ukázal,
  • unset smaže proměnnou z paměti shellu.

Kontrola délky uloženého tokenu, opět bez jeho vypsání:

shell @ server
kubectl -n cloudflared get secret tunnel-token -o jsonpath='{.data.token}' | base64 -d | wc -c

5. Konektor cloudflared

Manifest cloudflared/cloudflared.yaml obsahuje Namespace a Deployment:

NastaveníProč
image cloudflare/cloudflared:2026.7.3 (pevná verze)žádné nečekané změny; aktualizace vědomě změnou tagu
--no-autoupdatev kontejneru se binárka nemá sama měnit, verzi určuje image
TUNNEL_TOKEN ze Secretu (secretKeyRef)token není v manifestu ani v gitu
--metrics 0.0.0.0:2000 + liveness /readyKubernetes restartuje konektor, když ztratí spojení
namespace restricted, uid 65532, read-only FS, drop ALLstejně přísné jako web
limit 128 Mibez swapu je limit nutnost
shell @ server
kubectl apply -f cloudflared/cloudflared.yaml
kubectl -n cloudflared logs deploy/cloudflared -f

Úspěšný start v logu:

text
INF Registered tunnel connection connIndex=0 ... location=prg01 protocol=quic
INF Registered tunnel connection connIndex=1 ... location=vie05 protocol=quic
INF Registered tunnel connection connIndex=2 ... location=prg03 protocol=quic
INF Registered tunnel connection connIndex=3 ... location=vie05 protocol=quic
SUMMARY: Environment is healthy. cloudflared will use 'quic' as primary protocol.
  • 4 spojení do 2 lokalit (Praha, Vídeň): výpadek jednoho datacentra Cloudflare tunel neshodí,
  • QUIC běží přes UDP 7844 ven a firewall ho pustil (pody mají povolený forward, viz k3s),
  • token se v logu zobrazuje jen jako TUNNEL_TOKEN:*****.

V Cloudflare se tunel přepne na Healthy.

6. Trasa (Published application)

Detail tunelu → Add route → Published application:

PoleHodnota
Subdomainprázdné (hlavní doména)
Domainsvejklab.dev
Pathprázdné
Service URLhttp://traefik.kube-system.svc.cluster.local:80

Proč tahle adresa: cloudflared běží uvnitř clusteru, takže používá vnitřní DNS jméno služby ve tvaru <service>.<namespace>.svc.cluster.local. Provoz jde do Traefiku, ne přímo na web. Ingress pak rozhodne, kam patří. Ingress webu má pravidlo bez hostu (catch-all), takže zachytí i svejklab.dev.

Další aplikaci tak stačí přidat novou trasou (grafana → stejná URL) a Ingressem s host: grafana.svejklab.dev.

DNS záznam (CNAME svejklab.dev → <tunnel-id>.cfargotunnel.com, na hlavní doméně díky CNAME flattening) vytvořil Cloudflare sám.

7. Nasazení vyčištěné verze a test

shell @ server
cd ~/homelab-docs
./deploy.sh 0.2.0

Test z mobilu přes mobilní data, ne přes domácí Wi-Fi. Jen tak je jisté, že web jde opravdu z internetu. V záhlaví musí být user@homelab, podle toho se pozná vyčištěná verze.

Bezpečnostní shrnutí

RizikoOpatření
útok na domácí síť přes otevřený portžádný port není otevřený, tunel je odchozí
únik tokenuSecret, read -s, nikde v souborech; při úniku Rotate token
kompromitace webustatické HTML, nginx bez root práv, read-only FS, CSP hlavičky
útok z kompromitovaného podu na clusterautomountServiceAccountToken: false, PSS restricted
přetížení / DDoSstojí před tím Cloudflare
prozrazení topologievyčištěný obsah

Co dál

  • Cloudflare Access (Zero Trust → Access → Applications) pro budoucí admin rozhraní, například Grafanu: přístup jen po přihlášení e-mailem,
  • Private hostname / WARP pro SSH k homelabu odkudkoli bez otevřených portů, jako alternativa k Tailscale.