apiVersion: v1 kind: Namespace metadata: name: monitoring --- apiVersion: apps/v1 kind: Deployment metadata: name: cluster-dashboard namespace: monitoring labels: app: cluster-dashboard spec: replicas: 1 selector: matchLabels: app: cluster-dashboard template: metadata: labels: app: cluster-dashboard spec: # Share host network so Tailscale IPs are reachable for SSH hostNetwork: true dnsPolicy: ClusterFirstWithHostNet # Pin to control plane — image is imported there nodeSelector: kubernetes.io/hostname: nixos tolerations: - key: node-role.kubernetes.io/control-plane operator: Equal value: "true" effect: NoSchedule containers: - name: dashboard image: localhost/cluster-dashboard:latest imagePullPolicy: Never ports: - containerPort: 8787 name: http env: - name: KUBECONFIG value: /kubeconfig/config volumeMounts: - name: kubeconfig mountPath: /kubeconfig readOnly: true - name: ssh-identity mountPath: /identity readOnly: true resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" readinessProbe: httpGet: path: /health port: 8787 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /health port: 8787 initialDelaySeconds: 10 periodSeconds: 30 volumes: - name: kubeconfig secret: secretName: dashboard-kubeconfig - name: ssh-identity secret: secretName: dashboard-ssh-key defaultMode: 0600 --- apiVersion: v1 kind: Service metadata: name: cluster-dashboard namespace: monitoring spec: selector: app: cluster-dashboard ports: - port: 80 targetPort: 8787 name: http type: ClusterIP --- # NodePort for direct access (no ingress needed) apiVersion: v1 kind: Service metadata: name: cluster-dashboard-nodeport namespace: monitoring spec: selector: app: cluster-dashboard ports: - port: 8787 targetPort: 8787 nodePort: 30820 name: http type: NodePort