# 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 `traefik`* — **so 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.** ```sh 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.** ```sh 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:192.168.5.10 https://bogus.hypertheory-labs.dev/ ```