One of the challenges of running a homelab with dozens of services is keeping track of what’s running and where. I recently deployed Homepage - a modern, fully static dashboard that automatically discovers services from Kubernetes ingresses.
Why Homepage?#
I evaluated several dashboard options including Homarr and Heimdall. Homepage stood out for a few reasons:
- Kubernetes-native service discovery - no manual configuration needed
- Real-time pod status - shows if services are actually running
- Clean, modern UI - dark theme, customizable layout
- Lightweight - just a static site with no database
The Setup#
Homepage runs as a simple deployment in Kubernetes with a ServiceAccount that has read access to the cluster. The magic happens through ingress annotations.
Base Deployment#
apiVersion: apps/v1
kind: Deployment
metadata:
name: homepage
spec:
replicas: 1
template:
spec:
serviceAccountName: homepage
initContainers:
- name: copy-config
image: busybox:1.36
command: ['sh', '-c', 'cp /config-source/* /config/']
volumeMounts:
- name: config-source
mountPath: /config-source
- name: config
mountPath: /config
containers:
- name: homepage
image: ghcr.io/gethomepage/homepage:v0.10.9
volumeMounts:
- name: config
mountPath: /app/config
volumes:
- name: config-source
configMap:
name: homepage-config
- name: config
emptyDir: {}The init container pattern is important here - Homepage needs a writable config directory to create log files, but ConfigMaps are read-only. We copy the config to an emptyDir volume at startup.
Service Discovery via Annotations#
The real power comes from ingress annotations. For each service you want on the dashboard, add these annotations:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: frigate
annotations:
gethomepage.dev/enabled: "true"
gethomepage.dev/name: "Frigate"
gethomepage.dev/group: "Home Automation"
gethomepage.dev/icon: "frigate.png"
gethomepage.dev/pod-selector: "app=frigate"The pod-selector annotation is crucial for status monitoring. Homepage uses this to find the actual pods and show whether they’re running. Without it, you’ll see “not found” errors.
ConfigMap for Layout#
The ConfigMap controls the dashboard layout and widgets:
apiVersion: v1
kind: ConfigMap
metadata:
name: homepage-config
data:
settings.yaml: |
title: Homelab
theme: dark
color: slate
headerStyle: clean
layout:
Home Automation:
style: row
columns: 4
Media:
style: row
columns: 4
Infrastructure:
style: row
columns: 4
widgets.yaml: |
- kubernetes:
cluster:
show: true
cpu: true
memory: true
showLabel: true
label: "cluster"
nodes:
show: true
cpu: true
memory: true
kubernetes.yaml: |
mode: cluster
services.yaml: |
[]
docker.yaml: |Note that services.yaml and docker.yaml must be valid YAML (even if empty). Setting services.yaml: "[]" prevents JavaScript errors from empty files.
Gotchas I Encountered#
1. Empty Config Files Cause JS Errors#
If services.yaml or docker.yaml are empty or contain only comments, Homepage throws a classList null reference error. Always set them to valid YAML.
2. Pod Selector Mismatches#
Homepage defaults to looking for pods with app.kubernetes.io/name=<ingress-name>. Most of my deployments use simpler labels like app=frigate. The gethomepage.dev/pod-selector annotation fixes this.
3. Icon Names#
Icons come from the Dashboard Icons project. Some names aren’t obvious:
- ArgoCD:
argo-cd.png(notargocd.png) - Z-Wave JS:
mdi-z-wave(Material Design Icons format)
Result#
Now every time I add a new service with the proper annotations, it automatically appears on my dashboard with real-time status. No more manually updating bookmark pages or forgetting what port something runs on.
The dashboard shows CPU/memory usage for the cluster and nodes, groups services logically, and immediately tells me if something is down. It’s become my starting point for accessing everything in the homelab.