kadet-hub logokadet-hub

Homepage on Kubernetes with Kapitan

homepage, a dashboard of links, service widgets and bookmarks, with its dashboard written in the inventory and optional service discovery in the cluster, designed in kadet-hub on homepage’s image and rendered by the kadet-hub kubernetes generator 1.14.0.

What it deploys

Required parameters

applications.homepage.allowed_hosts is the value of HOMEPAGE_ALLOWED_HOSTS (DEC-227 in the spec): the hosts, comma-separated and without spaces, that clients name in the Host header of their requests, each with the port where the URL has one, such as home.example.com behind an Ingress or homepage.homepage.svc:3000 inside the cluster. homepage answers every other host with 400, its health check too. localhost:3000 and 127.0.0.1:3000 are always allowed, so kubectl port-forward to the local port 3000 works without an entry. * turns the check off, which homepage’s documentation advises against. Without the parameter the compile stops with Interpolation key and its name.

Include

A target includes the recipe and its options as classes and can override anything the recipe sets:

classes:
  - recipes.homepage
  # Optional: service discovery and statistics from the cluster's API.
  - recipes.homepage.kubernetes
parameters:
  namespace: homepage
  kapitan:
    vars:
      target: homepage
  applications:
    homepage:
      allowed_hosts: home.example.com
      settings:
        title: Home
      services:
        - Media:
            - Jellyfin:
                href: https://jellyfin.example.com/
                description: Media server
      bookmarks: []
      widgets: []

Fetching it

The quickstart goes through these steps on a kind cluster with the uptime-kuma recipe, from installing Kapitan to the running app.

A separate fetch inventory pins the recipe by version and digest. This is fetch/targets/recipes.yml for version 1.0.0:

parameters:
  kapitan:
    dependencies:
      - type: oci
        source: ghcr.io/kadet-hub/recipes/homepage:1.0.0@sha256:6dc78d48b0212afabcec930925c4c58a9e11747eec93d2eb374901b8e2080fcb
        output_path: inventory/classes/recipes/homepage

The first command fetches the recipe into inventory/classes/recipes/homepage, the second the generators and compiles:

kapitan compile --inventory-path fetch --fetch --output-path .
kapitan compile --fetch

The first command exits with status 1 because the fetch target compiles nothing. Kapitan's --fetch checks neither the digest nor the attestation. This verifies the attestation of the recipe:

gh attestation verify \
  oci://ghcr.io/kadet-hub/recipes/homepage@sha256:6dc78d48b0212afabcec930925c4c58a9e11747eec93d2eb374901b8e2080fcb \
  --owner kadet-hub \
  --cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main

The attested index must also list each digest under its recipe with the pinned version, and not as yanked. Verifying by hand shows how to read it.

Settings

The dashboard is four parameters, each written as YAML to its file in /app/config (DEC-226). Each takes what homepage’s documentation shows for its file, as inventory YAML. The recipe sets each empty, so the dashboard is what the target writes.

homepage file Inventory Default
settings.yaml applications.homepage.settings {}
services.yaml applications.homepage.services []
bookmarks.yaml applications.homepage.bookmarks []
widgets.yaml applications.homepage.widgets []
kubernetes.yaml, with the option kubernetes applications.homepage.kubernetes mode: cluster

Kapitan’s resolver yaml writes the keys of a mapping in sorted order. homepage orders the groups of the page by layout in settings.yaml, so write layout as a list of one-key mappings, which homepage reads in list order:

settings:
  layout:
    - Media:
        style: row
    - Infrastructure:
        style: column

A list set in the target is appended to the list of the classes before it. To replace a list a class sets, set it to ~ in a class included after that class, and the new list in the target.

The ConfigMap’s name carries a hash of its content, so a changed dashboard rolls out a new pod. Earlier ConfigMaps stay in the namespace until they are deleted. homepage reads secrets for widgets from {{HOMEPAGE_VAR_*}} and {{HOMEPAGE_FILE_*}} placeholders in these files. Set such variables through components.homepage.env and components.homepage.secrets with a ref, rather than writing a credential into the dashboard.

Setting Inventory
homepage version components.homepage.image, tag and digest
exposure components.homepage.service.type, or a route or Ingress of the target to the Service homepage on port 3000
other variables, such as LOG_LEVEL or homepage’s login with HOMEPAGE_AUTH_ENABLED components.homepage.env.<variable>, credentials through components.homepage.secrets
custom.css, custom.js and other files of /app/config components.homepage.config_maps.config.data.<file>.value with a mount in components.homepage.volume_mounts, as homepage.yml mounts settings.yaml
resources components.homepage.resources

homepage serves the dashboard without a login, as its default (DEC-229). Anyone who reaches the Service sees the dashboard and what its widgets fetch from the services they name. homepage’s documentation asks for a reverse proxy with authentication, or its own login, in front of a public deployment.

/app/config is an emptyDir. homepage writes its log there and copies each file it reads and the recipe does not mount, such as docker.yaml, from the defaults in its image. The dashboard’s files are mounted over it from the ConfigMap.

Proof

check homepage --level deploys with tests/target.yml, which includes the option kubernetes, on kind for each Kubernetes minor CI tests, with check 0.17.0. The image has no Kubernetes objects to compare with, so the recipe is proven by deploying it (RCP-44). check lint accepts the ClusterRole because it only reads and the binding because it binds that ClusterRole (RCP-308).

tests/smoke checks that the pod passes Pod Security restricted and starts a pod of homepage’s image, which carries node. From there, through the Service homepage on port 3000, it

Not tested: the page in a browser, service widgets, Gateway API and Traefik discovery, Pod statistics, which need a metrics server, and homepage’s login.

Versions

The recipe's files are in kadet-hub/recipes. All recipes