--- # Homepage (gethomepage) — the apex landing dashboard at https://home.keithjackson.dev. Plain # manifests (not a HelmRelease): the whole app is a single stateless Deployment + a config ConfigMap, # and hand-authoring the ConfigMap gives exact control over the service links without guessing a # chart's values schema (same plain-manifest style as apps/vms/unifi). Rendered by its OWN non-gating # post-platform Flux Kustomization (clusters/base/homepage.yaml, dependsOn platform-config, # wait:false) — a leaf UI must never sit in the gating `platform` tier (DECISIONS-LOG: "Headlamp # gating stall"). apiVersion: apps/v1 kind: Deployment metadata: name: homepage namespace: homepage labels: app.kubernetes.io/name: homepage spec: replicas: 1 revisionHistoryLimit: 3 selector: matchLabels: app.kubernetes.io/name: homepage template: metadata: labels: app.kubernetes.io/name: homepage annotations: # Re-roll the pod when the config changes (ConfigMap subPath mounts do NOT hot-reload). checksum/config: config-v2 spec: automountServiceAccountToken: false securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault containers: - name: homepage # Renovate maintains this pin (ghcr.io/gethomepage/homepage). Config lives in /app/config. image: ghcr.io/gethomepage/homepage:v2.1.2 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 3000 protocol: TCP env: # Referenced by HOMEPAGE_ALLOWED_HOSTS below so the in-pod liveness/readiness probes # (which hit the pod IP, not the public host) are accepted by Homepage's host guard. - name: MY_POD_IP valueFrom: fieldRef: fieldPath: status.podIP # REQUIRED by Homepage >= 1.0 — requests whose Host header is not in this list get 403. # The public apex host (served via the Gateway) + the pod IP (for kubelet probes). - name: HOMEPAGE_ALLOWED_HOSTS value: "${BASE_DOMAIN},$(MY_POD_IP):3000" resources: requests: cpu: 25m memory: 128Mi limits: memory: 256Mi securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL # NOTE: readOnlyRootFilesystem intentionally left unset — Homepage (Next.js) writes a # runtime build/cache under /app/.next at boot. Same deferred-hardening note as headlamp. livenessProbe: httpGet: path: /api/healthcheck port: http initialDelaySeconds: 5 periodSeconds: 15 readinessProbe: httpGet: path: /api/healthcheck port: http initialDelaySeconds: 5 periodSeconds: 15 volumeMounts: # Homepage expects each config file present; mount them read-only by subPath so the rest # of /app/config stays writable (it writes ./logs there at runtime). - { name: homepage-config, mountPath: /app/config/settings.yaml, subPath: settings.yaml, readOnly: true } - { name: homepage-config, mountPath: /app/config/services.yaml, subPath: services.yaml, readOnly: true } - { name: homepage-config, mountPath: /app/config/bookmarks.yaml, subPath: bookmarks.yaml, readOnly: true } - { name: homepage-config, mountPath: /app/config/widgets.yaml, subPath: widgets.yaml, readOnly: true } - { name: homepage-config, mountPath: /app/config/docker.yaml, subPath: docker.yaml, readOnly: true } - { name: homepage-config, mountPath: /app/config/kubernetes.yaml, subPath: kubernetes.yaml, readOnly: true } # proxmox.yaml is ALSO a required file in Homepage v2.1.2's skeleton — provide it so the # app never copies a skeleton into the read-only /app/config (EACCES crash otherwise). - { name: homepage-config, mountPath: /app/config/proxmox.yaml, subPath: proxmox.yaml, readOnly: true } - { name: homepage-config, mountPath: /app/config/custom.css, subPath: custom.css, readOnly: true } - { name: homepage-config, mountPath: /app/config/custom.js, subPath: custom.js, readOnly: true } # Writable scratch for Homepage's runtime logs (it writes /app/config/logs). - { name: logs, mountPath: /app/config/logs } volumes: - name: homepage-config configMap: name: homepage - name: logs emptyDir: {}