NOTE / TERRAIN

Dashy on Kubernetes: A Front Door for the Homelab.

Dashy is the front door to my homelab, but it is not the foundation.

That distinction is deliberate. The dashboard collects links and gives the growing application estate a readable shape. If Dashy is unavailable, the services behind it should still work. A broken map is inconvenient; it should not close the roads.

Browser
   │
   ▼
Traefik
   │
   ▼
Dashy
   │
   ├──► platform tools
   ├──► security tools
   ├──► data applications
   └──► media applications

That makes Dashy operationally simple and editorially important. It is where a collection of workloads starts to feel like one homelab.

Why a dashboard?

Bookmarks work until they do not. As services move, multiply or gain different access policies, a curated launch page becomes easier to maintain than browser state scattered across devices.

Dashy gives me one place to express:

what the service is called
what it is for
where its entry point lives
which group it belongs to

The dashboard is also a quiet inventory check. A dead link often reveals an ingress change, a retired application or an undocumented rename.

I avoid turning it into a control plane. Dashy does not own the service, the route or the authentication policy. It points to them.

Current deployment

The Kubernetes shape is compact:

single Deployment
    │
    ├── Dashy container
    ├── volume-permissions init container
    └── 500 MiB persistent claim

Service
    │
    ▼
Traefik route

Dashy runs as one replica. The main container mounts /app/public from a 500 MiB Longhorn-backed claim. A BusyBox init container fixes ownership on that volume before Dashy starts.

The pod runs with user and group ID 1000 and the Europe/Paris timezone. Those are small details, but mismatched permissions are a common reason a dashboard can read its initial state and then fail when it tries to update it.

The current Dashy image is not pinned and uses Always pull policy. The permissions helper is pinned to BusyBox 1.31.1.

Configuration is content

For this service the persistent files are not a database in the usual sense. They are the dashboard’s content and presentation state.

I treat that material as curated configuration:

links
groups
labels
icons
layout

A restore that brings the pod back but loses the catalogue produces a technically healthy blank dashboard. That is why the volume contents belong in backup, even though the application itself is disposable.

There is also a privacy boundary. The public story can name the products in the lab. The private dashboard configuration can contain routes and labels that should not be published. Backing it up does not mean copying it into Git without review.

Authentication and failure boundaries

Traefik owns the HTTPS route, and the authentication layer can protect the page before Dashy receives the request.

I test those concerns separately:

route and TLS
→ authentication flow
→ Dashy page
→ one representative destination link

A redirect loop is usually an ingress or authentication integration problem. A page with broken cards is usually dashboard content. A working card that opens a dead service is a downstream application problem.

Keeping those boundaries visible prevents a harmless dashboard issue from becoming an unnecessary restart of half the cluster.

Operating Dashy

The first Kubernetes checks are:

kubectl get deploy,pod,svc,pvc,ingressroute -l app=dashy
kubectl logs deploy/dashy -c dashy --tail=200
kubectl logs deploy/dashy -c volume-permissions --tail=100

If the pod is stuck in init, I inspect the claim and file ownership. If the pod is Ready but the page is unavailable, I inspect the Service and Traefik route. If the page loads with missing content, I stop before saving changes and verify which volume is mounted.

Init:Error
  └── volume attachment or ownership

CrashLoopBackOff
  └── application startup or incompatible persisted content

Ready, no page
  └── Service or ingress

Ready, empty page
  └── wrong, empty or unreadable persistent state

For a dashboard, an end-to-end health check opens the page and follows at least one link from each major group. That catches stale content which pod probes cannot see.

Backup and recovery

The useful backup set is small:

Dashy persistent content
+
runtime access policy
+
Kubernetes manifests

Git or the manifest repository can recreate the Deployment, Service and route. A snapshot or file backup must preserve the curated content.

My recovery order is:

1. Restore the persistent content
2. Recreate the PVC if necessary
3. Apply the workload and route
4. Confirm the init container completes
5. Open Dashy through the normal authentication path
6. Follow representative links

The final link test is important. Restoring a year-old dashboard perfectly can still produce a page full of obsolete routes.

Upgrades

The unpinned image and Always pull policy mean a pod restart can also be an application upgrade. That is convenient until it becomes surprising.

Before restarting during an incident, I record the current image ID:

kubectl get pod -l app=dashy \
  -o jsonpath='{range .items[*].status.containerStatuses[*]}{.imageID}{"\n"}{end}'

The safer future state is an explicit Dashy version updated through Git. That turns a restart back into a restart and makes rollback predictable.

Things worth remembering

At the time of writing:

Workload       one Deployment
Storage        500 MiB Longhorn claim
Init           BusyBox permissions helper
Ingress        Traefik with shared access control
Image policy   unpinned, always pull
Recovery test  page plus representative destination links

Dashy’s best feature is not that it makes the homelab look organised. It is that the organisation remains useful when the underlying estate changes.