Files
homelab-cluster/.loom/published/public-access.md
T
jeffryandClaude Opus 5 804ed08b84 take the node address out of a public example
An internal IP in a public document is not routable and still names the subnet,
and the example does not need a literal — the check is that a hostname with no
listener returns 404, and any node address demonstrates it.

Caught by grepping the fresh public history for anything version- or
address-shaped, which is a thing worth doing once per repository that changes
visibility.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 15:20:28 -04:00

3.5 KiB

Put something on the internet, the way production would

This cluster is staging for the production cluster, and that is the reason it exists rather than a use it happens to have.

Production's route manifests apply here unmodified. The only difference between the two Gateway definitions is the TLD — .dev here, .com there. Secret names are identical.

So the point of this page is what to leave out, not what to add.


Carry no hostname of your own

Routes pin to a listener by sectionName and inherit its hostname. That is what makes them portable — a route that carries its own hostnames: stops inheriting and stops working in the other environment.

Four listeners, matching production one for one: web (plain HTTP), and websecure, theoria and notes over HTTPS. Pin to the one whose hostname you want.

One route is the exception and keeps its hostname: the HTTP-to-HTTPS redirect, because the web listener declares no hostname at all — dropping it there would widen the redirect from one host to every host.

The Gateway is not in the controller's namespace, on purpose

The Gateway lives in traefik; the controller runs in traefik-system.

Only the Gateway's namespace affects how parentRefs resolve, and production names traefikso production's refs resolve here verbatim. Moving the controller would have meant an uninstall against a live service for something no application can observe.

Plain HTTP does not answer for everything

Only one hostname redirects from HTTP. The others have no route on the web listener and return 404 on plain HTTP. That mirrors production exactly, and it is a consequence of the exception above rather than an oversight.

A typo attaches to nothing, silently

A route with a wrong sectionName binds to no listener and reports nothing.

kubectl get gateway traefik-gateway -n traefik \
  -o jsonpath='{range .status.listeners[*]}{.name}: attached={.attachedRoutes}{"\n"}{end}'

attachedRoutes is the number that actually bound. Check it, not whether the route exists.

TLS is issued here and only terminated here

cert-manager owns every certificate, via DNS-01 against Cloudflare. Do not configure the proxy's own ACME resolver.

A certificate issues without the hostname resolving publicly, because the challenge is DNS-based. Iterate against the staging issuer — production rate limits are easy to hit during a rebuild.

A failed issuance backs off starting at one hour, and deleting the CertificateRequest does not reset it — it looks hung and is not. Delete and re-apply the Certificate.

The older path still works

One service is on a plain Ingress rather than the Gateway, and stays that way until it is migrated. Production uses no Ingress at all; when that last one moves, the environments match exactly. The divergence has an explicit end state, so it cannot quietly become permanent.


Checking this is still true

Verified 2026-09-03.

kubectl get gatewayclass                   # ACCEPTED=True
kubectl get gateway -n traefik             # PROGRAMMED=True, with an address
curl -sS -o /dev/null -w "%{http_code}\n" https://class.hypertheory-labs.dev/

# A hostname with no listener MUST 404. A 200 means a route was widened.
curl -skS -o /dev/null -w "%{http_code}\n" \
  --resolve bogus.hypertheory-labs.dev:443:<any node IP> https://bogus.hypertheory-labs.dev/