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]>
105 lines
4.7 KiB
YAML
105 lines
4.7 KiB
YAML
---
|
|
# 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: "home.keithjackson.dev,$(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: {}
|