Skip to content
GitHub stars

Dashboard ​

Lynq Dashboard is a web UI that shows the Hub → Form → Node topology and resource status. Use it to inspect reconciliation state, identify degraded nodes, and drill into individual resources.

Time to working

~2 minutes with Docker. ~10 minutes for an in-cluster Kubernetes deployment.

How It Works ​

  • The Dashboard reads LynqHub, LynqForm, and LynqNode CRs via the Kubernetes API.
  • It renders the Hub → Form → Node hierarchy and shows resource status (Ready, Degraded, Failed counts) per node.
  • No data is persisted — it's a read-only view of cluster state.

Choose Installation Method ​

MethodUse CaseDifficulty
Docker (Local)Quick local testingEasy
Local Dev ServerDashboard developmentEasy
Kubernetes DeploymentRunning in clusterMedium
Helm ChartProduction deploymentMedium

Docker (Local) ​

Run the Dashboard on your local machine and connect to a cluster using your existing kubeconfig.

Basic Usage ​

bash
docker run -d \
  --name lynq-dashboard \
  -p 8080:8080 \
  -v ~/.kube/config:/root/.kube/config:ro \
  -e APP_MODE=local \
  ghcr.io/k8s-lynq/lynq-dashboard:latest

Open http://localhost:8080 in your browser

Using a Specific Context ​

To use a specific context from a kubeconfig with multiple clusters:

bash
docker run -d \
  --name lynq-dashboard \
  -p 8080:8080 \
  -v ~/.kube/config:/root/.kube/config:ro \
  -e APP_MODE=local \
  ghcr.io/k8s-lynq/lynq-dashboard:latest \
  /app/bff-server -mode local -context my-cluster-context

EKS or Exec-Plugin Clusters (AWS, GKE, etc.) ​

If your kubeconfig uses an exec-based credential plugin (e.g. aws-iam-authenticator, gke-gcloud-auth-plugin), the plugin binary won't be available inside the Docker container. Use kubectl proxy to handle authentication on the host side instead.

Terminal 1 — start the proxy with host access enabled:

bash
kubectl proxy --port=8001 \
  --context=<your-context> \
  --accept-hosts='.*'

Terminal 2 — create a simple kubeconfig pointing to the proxy and run the Dashboard:

bash
cat > /tmp/lynq-proxy.kubeconfig << 'EOF'
apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://host.docker.internal:8001
  name: proxy
contexts:
- context:
    cluster: proxy
    user: ""
  name: proxy
current-context: proxy
users: []
EOF

docker run --rm -p 8080:8080 \
  -v /tmp/lynq-proxy.kubeconfig:/app/.kube/config:ro \
  -e KUBECONFIG=/app/.kube/config \
  -e APP_MODE=local \
  ghcr.io/k8s-lynq/lynq-dashboard:latest \
  /app/bff-server -mode local -addr :8080 -context proxy

Open http://localhost:8080 in your browser. kubectl proxy handles all cluster authentication transparently — no credentials enter the container.

TIP

--accept-hosts='.*' is required because Docker containers connect via host.docker.internal, not 127.0.0.1. Without it, kubectl proxy rejects the requests with 403 Forbidden.

Environment Variables ​

VariableDefaultDescription
APP_MODEclusterRun mode. local or cluster
STATIC_DIR/app/publicPath to UI static files

Stop and Cleanup ​

bash
docker stop lynq-dashboard
docker rm lynq-dashboard

Local Dev Server ​

Run the BFF and UI dev server directly from source. Best for dashboard development or when you want hot-reload.

Prerequisites ​

  • Go 1.21+
  • Node.js 20+
  • Source code cloned: git clone https://github.com/k8s-lynq/lynq && cd lynq/dashboard

EKS or Exec-Plugin Clusters ​

If your cluster uses an exec-based credential plugin (aws-iam-authenticator, gke-gcloud-auth-plugin, etc.), use kubectl proxy to handle authentication on the host side.

Terminal 1 — start kubectl proxy:

bash
kubectl proxy --port=8001 --context=<your-context>

Terminal 2 — create a kubeconfig pointing to the proxy and start the BFF:

bash
cat > /tmp/lynq-proxy.kubeconfig << 'EOF'
apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://localhost:8001
  name: proxy
contexts:
- context:
    cluster: proxy
    user: ""
  name: proxy
current-context: proxy
users: []
EOF

cd dashboard
KUBECONFIG=/tmp/lynq-proxy.kubeconfig go run ./bff/cmd/server -mode local -addr :8080 -context proxy

Terminal 3 — start the UI dev server:

bash
cd dashboard
npm --prefix ui run dev

Open http://localhost:5173 (or the next available port if 5173 is in use).

TIP

Unlike the Docker method, kubectl proxy does not need --accept-hosts='.*' here because the BFF runs on the host and connects to localhost:8001 directly.

Standard Clusters (kubeconfig with certificates) ​

If your kubeconfig uses standard certificate-based auth:

Terminal 1 — start the BFF:

bash
cd dashboard
make dev-bff
# or: go run ./bff/cmd/server -mode local -context <your-context>

Terminal 2 — start the UI dev server:

bash
cd dashboard
make dev-ui

Open http://localhost:5173.

Kubernetes Deployment ​

Run the Dashboard as a Deployment within the cluster. It uses in-cluster authentication, so no separate kubeconfig is needed.

1. Create Namespace (Optional) ​

If Lynq Operator is already installed, the lynq-system namespace exists:

bash
kubectl create namespace lynq-system --dry-run=client -o yaml | kubectl apply -f -

2. RBAC Setup ​

Grant permissions for the Dashboard to read Lynq CRDs and related resources:

yaml
# dashboard-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: lynq-dashboard
  namespace: lynq-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: lynq-dashboard
rules:
  # Lynq CRDs
  - apiGroups: ["operator.lynq.sh"]
    resources: ["lynqhubs", "lynqforms", "lynqnodes"]
    verbs: ["get", "list", "watch"]
  # Managed resources (read-only)
  - apiGroups: [""]
    resources: ["services", "configmaps", "secrets", "persistentvolumeclaims", "serviceaccounts", "events", "namespaces"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "statefulsets", "daemonsets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["batch"]
    resources: ["jobs", "cronjobs"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["policy"]
    resources: ["poddisruptionbudgets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["autoscaling"]
    resources: ["horizontalpodautoscalers"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: lynq-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: lynq-dashboard
subjects:
  - kind: ServiceAccount
    name: lynq-dashboard
    namespace: lynq-system
bash
kubectl apply -f dashboard-rbac.yaml

3. Deployment and Service ​

yaml
# dashboard-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lynq-dashboard
  namespace: lynq-system
  labels:
    app.kubernetes.io/name: lynq-dashboard
    app.kubernetes.io/component: dashboard
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: lynq-dashboard
  template:
    metadata:
      labels:
        app.kubernetes.io/name: lynq-dashboard
    spec:
      serviceAccountName: lynq-dashboard
      securityContext:
        runAsNonRoot: true
        runAsUser: 65532
        runAsGroup: 65532
        fsGroup: 65532
      containers:
        - name: dashboard
          image: ghcr.io/k8s-lynq/lynq-dashboard:latest
          args:
            - "-mode"
            - "cluster"
            - "-addr"
            - ":8080"
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /readyz
              port: http
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 256Mi
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL
---
apiVersion: v1
kind: Service
metadata:
  name: lynq-dashboard
  namespace: lynq-system
  labels:
    app.kubernetes.io/name: lynq-dashboard
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app.kubernetes.io/name: lynq-dashboard
bash
kubectl apply -f dashboard-deployment.yaml

4. Access Methods ​

Port Forward (Development/Testing) ​

bash
kubectl port-forward -n lynq-system svc/lynq-dashboard 8080:80
# Open http://localhost:8080 in browser

Ingress (Production) ​

yaml
# dashboard-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: lynq-dashboard
  namespace: lynq-system
  annotations:
    # Modify according to your Ingress Controller
    kubernetes.io/ingress.class: nginx
spec:
  rules:
    - host: lynq.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: lynq-dashboard
                port:
                  number: 80
  tls:
    - hosts:
        - lynq.example.com
      secretName: lynq-dashboard-tls
bash
kubectl apply -f dashboard-ingress.yaml

Helm Chart ​

The Dashboard is included in the Lynq Helm Chart.

Installation ​

bash
# Add Helm repo
helm repo add lynq https://k8s-lynq.github.io/lynq
helm repo update

# Install with Dashboard
helm install lynq lynq/lynq \
  --namespace lynq-system \
  --create-namespace \
  --set dashboard.enabled=true

Key Configuration Options ​

yaml
# values.yaml
dashboard:
  enabled: true
  replicaCount: 1
  image:
    repository: ghcr.io/k8s-lynq/lynq-dashboard
    tag: latest
    pullPolicy: IfNotPresent
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      cpu: 200m
      memory: 256Mi
  service:
    type: ClusterIP
    port: 80
  ingress:
    enabled: false
    className: nginx
    hosts:
      - host: lynq.example.com
        paths:
          - path: /
            pathType: Prefix
    tls: []

Enable Ingress ​

bash
helm upgrade lynq lynq/lynq \
  --namespace lynq-system \
  --set dashboard.enabled=true \
  --set dashboard.ingress.enabled=true \
  --set dashboard.ingress.hosts[0].host=lynq.example.com

Feature Customization ​

To hide the Documentation and GitHub links at the bottom of the Dashboard:

yaml
# Add environment variables to Deployment
env:
  - name: VITE_HIDE_DOCS_LINK
    value: "true"
  - name: VITE_HIDE_GITHUB_LINK
    value: "true"

Change external links to custom URLs:

yaml
env:
  - name: VITE_DOCS_URL
    value: "https://internal-docs.company.com/lynq"
  - name: VITE_GITHUB_URL
    value: "https://github.internal.company.com/platform/lynq"

Troubleshooting ​

CRDs Not Found ​

If the Dashboard shows "No resources found":

  1. Verify Lynq Operator CRDs are installed:
bash
kubectl get crd | grep lynq
  1. Verify RBAC permissions are correct:
bash
kubectl auth can-i list lynqhubs --as=system:serviceaccount:lynq-system:lynq-dashboard

Permission Errors ​

bash
# Check if ServiceAccount has correct permissions
kubectl describe clusterrolebinding lynq-dashboard

Connection Errors ​

If Docker local mode cannot connect to the cluster:

  1. Verify the kubeconfig file is mounted correctly
  2. Verify the current context points to an accessible cluster:
bash
kubectl config current-context
kubectl cluster-info

See Also ​

  • Quickstart — Getting started with Lynq Operator.
  • Monitoring — Prometheus/Grafana integration alongside the Dashboard.
  • Architecture — Hub → Form → Node data model the Dashboard visualizes.