This repository is now the public half of a split: six documents saying what to decide, and one gap. How the cluster is built moved to jeffry/homelab-impl, which is private because its README is an inventory of chart versions and image tags. History starts here deliberately, and not as tidiness. The previous history contained that inventory, and this repository is public — a deletion commit would have removed it from the tree and left it in the log. A fresh root can only carry what is in it. Two pointers rewritten rather than deleted: private-access.md and .loom/README.md both directed a reader to TAILSCALE.md and README.md at the root, which are now private. They now say a fuller reference exists, that it is private, and how to ask — because a public page naming a private thing as its answer is the failure this project has now hit four times. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
90 lines
3.5 KiB
Markdown
90 lines
3.5 KiB
Markdown
# 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/
|
|
```
|