Homepage
Homepage
Self-hosted dashboard for the homelab. Runs as homepage in the default namespace of the k3s cluster, fronted by Traefik, deployed and kept in sync by ArgoCD.
URLs: https://homepage.prod-k3s.iot-proxmox.net / https://www.homepage.prod-k3s.iot-proxmox.net
Stack
| Item | Value |
|---|---|
| Image | ghcr.io/gethomepage/homepage:v2.2.0 |
| Namespace | default |
| Replicas | 1, no nodeSelector (no PVC to stay pinned to) |
| Service | ClusterIP, port 8888 → container 3000 |
| Config | ConfigMap homepage, mounted file-by-file via subPath |
| Secrets | Secret/homepage-secret (~60 HOMEPAGE_VAR_* keys), via envFrom |
| GitOps | ArgoCD Application — repo Homepage.git, branch k3s |
| Reloader | reloader.stakater.com/auto: "true" — pod restarts when the ConfigMap or Secret changes |
Config delivery
Rather than mounting the whole ConfigMap over /app/config, each file Homepage expects is mounted individually via subPath, so Homepage’s own default files for anything not explicitly provided stay intact:
1
2
3
4
5
6
7
8
9
/app/config/
├── settings.yaml # theme, layout, background
├── services.yaml # every tile, grouped
├── widgets.yaml # top-bar clock/search/cluster-stats
├── kubernetes.yaml # mode: cluster
├── bookmarks.yaml # empty
├── docker.yaml # empty — Docker socket integration unused, Portainer covers this
├── custom.css # empty
└── custom.js # empty
All widget URLs, usernames, passwords, and API keys referenced via HOMEPAGE_VAR_* template interpolation live in homepage-secret — checked into the source repo as stringData, same risk profile as any other secret in this workspace, just unusually wide-reaching since it holds credentials for nearly every service on the dashboard.
RBAC
A dedicated ServiceAccount/ClusterRole/ClusterRoleBinding (homepage) grants cluster-scoped, read-only get/list on namespaces, pods, nodes, ingresses, ingressroutes (traefik.io), httproutes/gateways (gateway.networking.k8s.io), and metrics.k8s.io (nodes, pods). This powers the kubernetes widget (cluster/node CPU + memory cards, widgets.yaml) — it’s cluster-scoped rather than namespaced because that widget reads node metrics cluster-wide. The ingresses/ingressroutes read access also supports Homepage’s optional auto-discovery via annotations, though every tile here is defined explicitly in services.yaml rather than relying on it.
Settings
- Theme: Dark / Slate, custom background image, blur
sm, brightness50 - Layout:
useEqualHeights: true, all groupsstyle: row - Widgets: Resources → replaced by the
kubernetescluster/node widget, plusdatetime(short time, XL text) andsearch(Google, new tab)
Service groups (services.yaml)
| Group | Cols | Tiles |
|---|---|---|
| Hypervisor | 3 | PVE (proxmox), PBS (proxmoxbackupserver), Cockpit |
| Network | 3 | Gateway (unifi), Traefik (traefik), Gatus (gatus) |
| Infrastructure | 3 | Portainer (portainer, kubernetes: true), TinyAuth, Filebrowser |
| Storage | 3 | TrueNAS (truenas, pools enabled), Longhorn, Pi-NAS Backup |
| Observability | 3 | Grafana (grafana), pgAdmin, DBeaver |
| Media | 2 | Jellyfin (jellyfin), Immich (immich) |
| Media-converter | 2 | Handbrake, Media Index (customapi → /api/counts: files/backlog/jobs) |
| Arr-config | 3 | qBittorrent (qbittorrent), Prowlarr (prowlarr), Profilarr |
| Arr | 2 | Radarr (radarr), Sonarr (sonarr) |
| Home Automation | 2 | Home Assistant (link only), Frigate (frigate, recent events) |
| Development | 3 | Code-Server, GitHub, Gitea (gitea) |
| Productivity | 2 | Invoice Ninja, Drawio |
| AI | 3 | n8n, Open WebUI, Ollama |
| DataTesting | 4 | ActiveMQ, Kafka, S3 (RustFS), FTP (SFTPGo) |
The Media/Media-converter/Arr/AI/DataTesting groups reflect apps added since the original Docker Compose deployment — the tile list is kept in sync with whatever is actually running on the cluster, not carried over as a static snapshot.
GitOps
argocd/apps/homepage.yaml defines the ArgoCD Application: it tracks the k3s branch of Homepage.git (the main branch still holds the legacy Docker Compose deployment for reference, and is Renovate’s default branch), applies every *.yaml in the repo root against the pre-existing default namespace, and syncs with prune: true + selfHeal: true — any manual kubectl change is reverted back to what’s committed. In practice, changes ship by committing to k3s, not by running kubectl apply by hand.
Renovate reads its config from main (its default branch) but is configured with baseBranchPatterns: ["main", "k3s"] so it also opens PRs against k3s, with kubernetes.fileMatch enabled so it tracks the Homepage image tag in deployment.yaml, and automerges minor/patch/pin/digest bumps.
