env-view: a custom Helm chart for per-environment service dashboards

Published: 2026-03-24

Every Kubernetes environment has a links.{env}.test.antonnovikov.com page — a static dashboard listing every service URL for that environment. It's built from a custom Helm chart called env-view, stored in charts/env-view/ in the infra repo. No external dependencies, no SPAs with build steps — just nginx serving rendered HTML from a ConfigMap.


What it is

A single-page app served by nginx. The chart renders an HTML page with cards for every service: Prometheus, Grafana, Kibana, APISIX admin, Headlamp, Hubble UI, Allure, SonarQube, and more.

Each environment's page shows only the services that exist there. infra shows Vault, Allure, SonarQube, Grafana. dev shows Prometheus, Kibana, Headlamp. The enabled/disabled state is controlled by Helm values.


HelmRelease in base

yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: env-view
spec:
  interval: 1h
  chart:
    spec:
      chart: ./charts/env-view
      version: 25.0.0
      sourceRef:
        kind: GitRepository
        name: flux-system
        namespace: flux-system
  values:
    image:
      tag: 2ec9214f
      pullPolicy: Always
    ingress:
      enabled: true
      annotations:
        description: "Просмотр сервисов и состояния кластера"
    environments:
      - name: Infra
        url: https://links.infra.test.antonnovikov.com
        enabled: true
      - name: Development
        url: https://links.dev.test.antonnovikov.com
        enabled: true
      - name: Test
        url: https://links.test.test.antonnovikov.com
        enabled: true
      - name: Demo
        url: https://links.demo.test.antonnovikov.com
        enabled: false

The environments list renders a navigation header — clicking a cluster name jumps to that cluster's env-view page.


Per-environment patch

yaml# projects/dev/kustomization/hub/patches/env-view.yaml
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: env-view
  namespace: dev
spec:
  values:
    ingress:
      host: links.dev.test.antonnovikov.com
    services:
      - name: Prometheus
        url: https://prometheus.dev.test.antonnovikov.com
        category: monitoring
      - name: Elasticsearch
        url: https://elasticsearch.dev.test.antonnovikov.com
        category: logs
      - name: Headlamp
        url: https://headlamp.dev.test.antonnovikov.com
        category: cluster
      - name: Hubble UI
        url: https://hubble.dev.test.antonnovikov.com
        category: network

Chart structure

charts/env-view/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── configmap.yaml    ← rendered HTML with service cards
│   ├── deployment.yaml   ← nginx serving the configmap
│   ├── service.yaml
│   ├── ingress.yaml
│   └── servicemonitor.yaml

templates/configmap.yaml renders the service list into an nginx index.html using Go templates. The deployment mounts the ConfigMap as /usr/share/nginx/html/index.html. No runtime logic — just a static file delivered by nginx.

The ServiceMonitor exposes nginx metrics (connections, request rate) to Prometheus.


Configmap template excerpt

yaml# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: env-view-html
data:
  index.html: |
    <!DOCTYPE html>
    <html>
    <head><title>{{ .Values.ingress.host }}</title></head>
    <body>
    <nav>
    {{- range .Values.environments }}
    {{- if .enabled }}
    <a href="{{ .url }}">{{ .name }}</a>
    {{- end }}
    {{- end }}
    </nav>
    <main>
    {{- range .Values.services }}
    <div class="card category-{{ .category }}">
      <a href="{{ .url }}">{{ .name }}</a>
    </div>
    {{- end }}
    </main>
    </body>
    </html>

Image versioning

The image.tag is a short git SHA: 2ec9214f. The image is built from the chart repo's Dockerfile and pushed to a private registry. Updating the UI means bumping the tag in base/web/env-view/helmrelease.yaml and committing.

With Flux Image Automation, this can be automated — Flux watches the registry tag and opens a PR when a new SHA is pushed.


Why a custom chart instead of a static page

  1. The service list changes as environments evolve — having it in Helm values means Flux manages it
  2. The environments navigation header needs to know about all clusters — defined once in base
  3. The ServiceMonitor is included — nginx availability tracked in Prometheus
  4. Patching per-environment URLs is clean with Kustomize, no manual file editing