Homepage v2.1.2 treats proxmox.yaml as a required config file (present in the image skeleton dir /app/src/skeleton alongside services/settings/ bookmarks/widgets/docker/kubernetes/custom.*). On startup it copies a default skeleton into /app/config for any required file that is missing. The ConfigMap provided every skeleton file except proxmox.yaml, so the pod tried to copyfile the proxmox skeleton into the read-only subPath-mounted /app/config and crashed with EACCES -> CrashLoopBackOff -> Gateway "no healthy upstream". Provide an empty proxmox.yaml in the ConfigMap and mount it via subPath like the other config files, so Homepage finds all required files and never writes at runtime. Verified the full required set against the running image's skeleton dir (9 files); proxmox.yaml was the only missing one. Bumped checksum/config annotation to re-roll the pod. Co-Authored-By: Claude Opus 4.8 <[email protected]>
platform/homepage
Homepage (gethomepage) — the apex landing dashboard at https://home.keithjackson.dev, aggregating links to the cluster's services.
- Serves: a single pane of glass for the homelab (docs/09 apex UI).
- Tier: non-gating post-platform Flux Kustomization
(
clusters/base/homepage.yaml,dependsOn: platform-config,wait: false) — mirrorsheadlamp/oauth2-proxy. A leaf UI must never gate theplatformtier (DECISIONS-LOG "Headlamp gating stall"). - Namespace:
homepage— a PLATFORM namespace (noapp-namespace=true), so the Kyverno app-scoped policies (default-deny gen, auth-mode, egress-class) do not apply; we ship an explicit default-deny + Cilium ingress policy as defense-in-depth.
What this component owns
Plain manifests (not a HelmRelease — the app is one stateless Deployment + a config ConfigMap):
| File | Purpose |
|---|---|
namespace.yaml |
homepage ns, gateway-access=shared |
configmap.yaml |
Homepage config (settings/services/bookmarks/widgets + empty docker/kubernetes + custom.css/js) |
deployment.yaml |
ghcr.io/gethomepage/homepage (non-root, subPath config mounts, HOMEPAGE_ALLOWED_HOSTS) |
service.yaml |
ClusterIP 80 -> 3000 |
networkpolicy.yaml |
default-deny (DNS-only egress) + Cilium Gateway-only ingress on 3000 |
httproute.yaml |
apex ingress on the Gateway https-apex listener |
Apex nuances (why this isn't just another *.home route)
- TLS: the apex
home.keithjackson.devis already a SAN on the wildcard Certificate (platform/gateway/wildcard-certificate.yaml) — no new cert. - Gateway listener: the wildcard listeners' hostname
*.home.keithjackson.devdoes not match the bare apex. This component's route binds a dedicatedhttps-apexlistener added to the shared Gateway (platform/gateway/gateway.yaml);http-apex-redirectcovers :80. HOMEPAGE_ALLOWED_HOSTS: Homepage >= 1.0 returns 403 for any Host not in this list — set tohome.keithjackson.dev+ the pod IP (for kubelet probes).
Forward-auth — FOLLOW-UP (not yet enforced)
The HTTPRoute is annotated auth-mode: forward-auth (intended posture: oauth2-proxy -> Keycloak).
The Gateway ext-authz attachment that actually enforces forward-auth does not exist yet — it is a
Phase-6 WS-NET TODO (platform/oauth2-proxy is the authenticator; there is no CiliumEnvoyConfig /
ext-authz wiring anywhere in the repo). Until that mechanism lands, Homepage — like every other
forward-auth app (jackett, qbittorrent, pihole, radarr, sonarr) — is reachable unauthenticated.
When the Gateway ext-authz pattern is built, attach it to this route (and the others) with
allowed-groups: platform-admins. PM decision flagged in the deploy report.
Deliberately deferred (avoid scope creep)
- Service-status widgets (Grafana/Plex/Sonarr/… API tiles) need per-service API-key secrets —
not wired; tiles are simple links. Add as SOPS secrets +
widgets.yamllater. - Kubernetes widget would need a read-only ServiceAccount + RBAC — skipped (
kubernetes.yamlempty,automountServiceAccountToken: false). - Brand icons (selfh.st/dashboard-icons) need world:443 egress — we use bundled
mdi-*icons so egress stays DNS-only.