Kubernetes Dashboard: Web UI Setup and Security

The Kubernetes Dashboard is a general-purpose web-based UI for Kubernetes clusters that lets you manage applications, troubleshoot running workloads, and inspect cluster resources without writing kubectl commands. While powerful and useful for development and operations teams, the Dashboard has a history of being misconfigured in ways that expose cluster admin access to the internet — the 2018 Tesla cryptojacking incident famously exploited an unsecured Dashboard. This guide covers proper installation, RBAC-scoped access, and secure ingress exposure.

Installing Kubernetes Dashboard

The Kubernetes Dashboard project publishes official manifests and a Helm chart. Dashboard v3 (released 2024) introduced a significant architecture change — the backend now runs as a separate service from the frontend, and authentication is handled by an OIDC-compatible auth proxy rather than via token pasting.

# Method 1: Official manifests (Dashboard v3)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v3.0.0/charts/kubernetes-dashboard.yaml

# Method 2: Helm chart (recommended for production)
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
helm repo update

helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard \
  --namespace kubernetes-dashboard \
  --create-namespace \
  --set app.ingress.enabled=false    # we'll configure ingress separately

# Verify all pods are running
kubectl get pods -n kubernetes-dashboard

After installation, the Dashboard namespace contains:

  • kubernetes-dashboard-web — the React frontend service
  • kubernetes-dashboard-api — the backend API service that talks to the Kubernetes API
  • kubernetes-dashboard-auth — the authentication/session management service
  • kubernetes-dashboard-metrics-scraper — collects resource usage metrics from the Metrics Server
Metrics Server prerequisite: The Dashboard displays CPU and memory usage graphs only if the Kubernetes Metrics Server is installed. Install it with kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml.

Access Methods: Port-Forward vs Ingress

For development clusters or occasional administrative access, kubectl proxy or port-forward is the safest option — access is authenticated via your kubeconfig and never exposed beyond your local machine.

# Method 1: kubectl proxy (recommended for developers)
# Starts a proxy server on localhost:8001 that uses your kubeconfig credentials
kubectl proxy

# Access at:
# http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard-web:443/proxy/

# Method 2: Port-forward directly to the Dashboard service
kubectl port-forward -n kubernetes-dashboard svc/kubernetes-dashboard-web 8443:443

# Access at https://localhost:8443
# Browser will warn about self-signed certificate — this is expected

For teams that need persistent access without running kubectl locally, expose the Dashboard via an ingress controller with HTTPS and authentication middleware. See the Secure Ingress Exposure section below.

RBAC: Admin and Read-Only Access

The Dashboard operates with the permissions of the authenticated user. Never create a ClusterRoleBinding that grants cluster-admin to the Dashboard's ServiceAccount — this was the root cause of the historical security incidents. Instead, create granular roles matching what each user or team actually needs.

# Read-only ClusterRole — view all resources but cannot modify anything
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: dashboard-viewer
rules:
  - apiGroups: [""]
    resources:
      - pods
      - pods/log
      - services
      - configmaps
      - namespaces
      - nodes
      - persistentvolumeclaims
      - persistentvolumes
      - events
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["batch"]
    resources: ["jobs", "cronjobs"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "list", "watch"]

---
# Namespace-scoped developer role — full access within their namespace only
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dashboard-developer
  namespace: team-payments
rules:
  - apiGroups: ["", "apps", "batch", "networking.k8s.io"]
    resources: ["*"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: [""]
    resources: ["pods/exec", "pods/portforward"]
    verbs: ["create"]
# Create a ServiceAccount for the Dashboard admin user
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dashboard-admin
  namespace: kubernetes-dashboard

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: dashboard-admin-binding
subjects:
  - kind: ServiceAccount
    name: dashboard-admin
    namespace: kubernetes-dashboard
roleRef:
  kind: ClusterRole
  name: dashboard-viewer    # read-only — change to cluster-admin only if truly needed
  apiGroup: rbac.authorization.k8s.io

Service Account Token Authentication

Dashboard v2 and earlier accepted a bearer token that users would paste into a login screen. In Kubernetes 1.24+, tokens are no longer automatically created for ServiceAccounts — you must create them explicitly as Secrets.

# Create a long-lived token for the dashboard admin ServiceAccount
apiVersion: v1
kind: Secret
metadata:
  name: dashboard-admin-token
  namespace: kubernetes-dashboard
  annotations:
    kubernetes.io/service-account.name: dashboard-admin
type: kubernetes.io/service-account-token
# Retrieve the token value
kubectl get secret dashboard-admin-token \
  -n kubernetes-dashboard \
  -o jsonpath='{.data.token}' | base64 --decode

# For temporary access, create a bound token with a short TTL
kubectl create token dashboard-admin \
  -n kubernetes-dashboard \
  --duration=1h
Token security: Long-lived tokens are credentials — treat them like passwords. Store them in a secrets manager (Vault, AWS Secrets Manager), rotate them regularly, and audit their usage via Kubernetes audit logs. Prefer short-lived tokens generated via kubectl create token for interactive access.

OIDC Integration for Team Access

For teams of more than a few people, individual token management is impractical. Integrate the Dashboard with your organisation's OIDC provider (Okta, Google Workspace, Azure AD, Keycloak) so that users log in with their existing SSO credentials.

# Configure the Kubernetes API server for OIDC
# Add these flags to kube-apiserver (kubeadm: edit /etc/kubernetes/manifests/kube-apiserver.yaml)
- --oidc-issuer-url=https://accounts.google.com
- --oidc-client-id=kubernetes-dashboard
- --oidc-username-claim=email
- --oidc-groups-claim=groups

# For EKS, configure OIDC via aws-auth ConfigMap or access entries
# For GKE, OIDC is configured at the cluster level
# Deploy oauth2-proxy in front of the Dashboard for SSO
helm upgrade --install oauth2-proxy oauth2-proxy/oauth2-proxy \
  --namespace kubernetes-dashboard \
  --set config.clientID=YOUR_OIDC_CLIENT_ID \
  --set config.clientSecret=YOUR_OIDC_CLIENT_SECRET \
  --set config.cookieSecret=$(openssl rand -base64 32) \
  --set config.upstreamUrl=https://kubernetes-dashboard-web.kubernetes-dashboard.svc.cluster.local:443 \
  --set extraArgs.provider=oidc \
  --set extraArgs.oidc-issuer-url=https://accounts.google.com \
  --set extraArgs.email-domain=yourcompany.com

Secure Ingress Exposure

Exposing the Dashboard via ingress requires HTTPS and strong authentication. Never expose the Dashboard on HTTP or without authentication — a publicly accessible Dashboard without auth is a critical security vulnerability.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
  annotations:
    # Require HTTPS (Traefik example)
    traefik.ingress.kubernetes.io/router.entrypoints: websecure
    traefik.ingress.kubernetes.io/router.tls: "true"
    # Forward auth to oauth2-proxy for SSO
    traefik.ingress.kubernetes.io/router.middlewares: kubernetes-dashboard-oauth2proxy@kubernetescrd
    # Restrict access to internal IPs only
    traefik.ingress.kubernetes.io/router.middlewares: ip-allowlist@kubernetescrd
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - dashboard.internal.example.com
      secretName: dashboard-tls
  rules:
    - host: dashboard.internal.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: kubernetes-dashboard-web
                port:
                  number: 443
Network isolation: Use DNS to restrict the Dashboard hostname to an internal domain that is not routable from the public internet (e.g., *.internal.example.com resolves only on VPN). Add an IP allowlist middleware as a second layer — block all source IPs except your VPN gateway and office ranges.

Dashboard Features and Use Cases

The Kubernetes Dashboard provides a visual interface for tasks that are cumbersome in kubectl:

  • Pod logs: Stream logs from any container in any pod, with the ability to filter by time range and keyword
  • Resource viewer: Browse all Kubernetes resources with their current state, labels, annotations, and events
  • Exec shell: Open an interactive shell inside a running container (subject to RBAC — requires pods/exec permission)
  • Scale deployments: Increase or decrease replica count with a single click
  • Rolling updates: Update container image tags via the Dashboard and observe the rollout progress
  • Resource usage graphs: CPU and memory usage trends per pod and namespace (requires Metrics Server)
  • YAML editor: View and edit raw YAML for any resource with syntax highlighting

The Dashboard is particularly valuable for on-call engineers who need to quickly investigate incidents without context-switching to a terminal, and for application developers who want to inspect their pods without learning advanced kubectl commands.

Dashboard Alternatives: Lens, Headlamp, Octant

The official Kubernetes Dashboard is not the only option for a cluster web UI. Several mature alternatives offer different trade-offs:

  • Lens: A desktop application (Electron) that connects to multiple clusters simultaneously. Rich UI, built-in metrics, and extension marketplace. Free for personal use; Lens Pro adds team features. No in-cluster component needed — runs entirely on your laptop using kubeconfig.
  • Headlamp: An open-source, extensible Kubernetes UI that can run as a web app (in-cluster) or as a desktop application. CNCF sandbox project. Designed for plugin extensibility.
  • k9s: Not a web UI but a terminal-based cluster navigator that provides a Dashboard-like experience in the terminal. Extremely fast, keyboard-driven, and loved by experienced operators.
# Install Headlamp as an in-cluster web UI
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
helm upgrade --install headlamp headlamp/headlamp \
  --namespace headlamp \
  --create-namespace \
  --set ingress.enabled=true \
  --set ingress.hosts[0].host=headlamp.internal.example.com
Recommendation: For individuals and small teams, Lens desktop app is the most productive choice — no in-cluster component to secure, works with any kubeconfig. For larger organisations that need a shared web UI accessible to non-kubectl users, the official Dashboard with proper OIDC integration and ingress security is the standard choice.