The Kubernetes Dashboard is a web interface for viewing and managing the workloads, Services, storage and logs of a cluster. Since version 7 it is installed only through Helm and is fronted by a small Kong gateway that proxies the UI and API containers. In this tutorial you will install the Dashboard with Helm, create an administrator account and a read-only account, generate tokens to log in, and open the UI through an SSH tunnel without exposing it to the Internet.
NoteThe upstream Kubernetes Dashboard project is no longer actively developed, and SIG UI recommends Headlamp for new deployments. The Helm chart used here still works on current clusters, but keep this in mind if you are choosing a long-term UI.
Prerequisites
To follow this tutorial, you will need:
- A running Kubernetes cluster and a server where
kubectlcan reach it with admin rights, for example a CubePath VPS running Ubuntu 24.04 with K3s or MicroK8s.kubectl get nodesshould return your nodes asReady. - A non-root user with
sudoprivileges on that server. - SSH access from your workstation to the server.
Step 1 - Installing Helm
Helm is the package manager for Kubernetes and the only supported way to install the Dashboard. On Ubuntu 24.04, install it from the snap store:
sudo snap install helm --classic
Check that the client works. The output is a single version string such as v3.xx.x+g... or v4.x.x+g...; both major versions work with this chart:
helm version --short
Helm uses the same kubeconfig as kubectl. If you use K3s, make sure KUBECONFIG points to a copy of /etc/rancher/k3s/k3s.yaml that your user can read, then confirm Helm can reach the cluster:
helm list -A
The command should return a table (possibly with existing releases) and no connection errors.
Step 2 - Installing the Kubernetes Dashboard
Add the official chart repository and refresh the index:
helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
helm repo update
Install the chart into its own namespace:
helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard \
--create-namespace --namespace kubernetes-dashboard
Using helm upgrade --install makes the command safe to rerun later when you want to upgrade the Dashboard.
Wait for the pods to start:
kubectl -n kubernetes-dashboard get pods
NAME READY STATUS RESTARTS AGE
kubernetes-dashboard-api-xxxxxxxxxx-xxxxx 1/1 Running 0 60s
kubernetes-dashboard-auth-xxxxxxxxxx-xxxxx 1/1 Running 0 60s
kubernetes-dashboard-kong-xxxxxxxxxx-xxxxx 1/1 Running 0 60s
kubernetes-dashboard-metrics-scraper-xxxxxxxxxx-xxxxx 1/1 Running 0 60s
kubernetes-dashboard-web-xxxxxxxxxx-xxxxx 1/1 Running 0 60s
List the Services. The entry point for the browser is kubernetes-dashboard-kong-proxy:
kubectl -n kubernetes-dashboard get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard-api ClusterIP 10.43.12.34 <none> 8000/TCP 70s
kubernetes-dashboard-auth ClusterIP 10.43.56.78 <none> 8000/TCP 70s
kubernetes-dashboard-kong-proxy ClusterIP 10.43.90.12 <none> 443/TCP 70s
kubernetes-dashboard-metrics-scraper ClusterIP 10.43.34.56 <none> 8000/TCP 70s
kubernetes-dashboard-web ClusterIP 10.43.78.90 <none> 8000/TCP 70s
The Service type is ClusterIP, so the Dashboard is not reachable from outside the cluster. Keep it that way.
Step 3 - Creating an administrator account
The Dashboard has no users of its own: you log in with a Kubernetes bearer token, and the Dashboard can do exactly what that token is allowed to do. Create a ServiceAccount bound to the built-in cluster-admin role for full administrative access.
nano dashboard-admin.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
Apply the manifest:
kubectl apply -f dashboard-admin.yaml
serviceaccount/admin-user created
clusterrolebinding.rbac.authorization.k8s.io/dashboard-admin-user created
WarningA
cluster-admintoken can read every Secret and delete anything in the cluster. Use it only for administration, keep token lifetimes short and never paste it into shared chats or tickets.
Step 4 - Creating a read-only account
For teammates who only need to look at workloads and logs, bind a second ServiceAccount to the built-in view ClusterRole. view grants read access to most namespaced resources but deliberately excludes Secrets.
nano dashboard-viewer.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: viewer-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: dashboard-viewer-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: view
subjects:
- kind: ServiceAccount
name: viewer-user
namespace: kubernetes-dashboard
kubectl apply -f dashboard-viewer.yaml
Confirm the permissions with kubectl auth can-i, impersonating the account:
kubectl auth can-i list pods --all-namespaces \
--as=system:serviceaccount:kubernetes-dashboard:viewer-user
kubectl auth can-i delete pods --all-namespaces \
--as=system:serviceaccount:kubernetes-dashboard:viewer-user
yes
no
To restrict someone to a single namespace, use a RoleBinding in that namespace instead of a ClusterRoleBinding, still referencing the view (or edit) ClusterRole.
Step 5 - Generating a login token
Create a short-lived token for the admin account. By default the token expires after one hour; --duration sets another lifetime:
kubectl -n kubernetes-dashboard create token admin-user --duration=8h
eyJhbGciOiJSUzI1NiIsImtpZCI6Ij...
Copy the whole string. Generate a token for the read-only account the same way by replacing admin-user with viewer-user.
If you really need a token that does not expire, for example for a monitoring screen, create a Secret of type kubernetes.io/service-account-token linked to the account:
nano viewer-token.yaml
apiVersion: v1
kind: Secret
metadata:
name: viewer-user-token
namespace: kubernetes-dashboard
annotations:
kubernetes.io/service-account.name: viewer-user
type: kubernetes.io/service-account-token
kubectl apply -f viewer-token.yaml
kubectl -n kubernetes-dashboard get secret viewer-user-token -o jsonpath='{.data.token}' | base64 -d; echo
Delete the Secret to revoke that token. Avoid long-lived tokens for the admin account.
Step 6 - Opening the Dashboard through an SSH tunnel
Instead of publishing the Dashboard, forward its Service to the server's loopback interface and reach it through SSH. On the server, start the port forward:
kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443
Forwarding from 127.0.0.1:8443 -> 8443
Forwarding from [::1]:8443 -> 8443
Leave it running. On your workstation, open a second terminal and create the tunnel, replacing your_user and your_server_ip:
ssh -N -L 8443:127.0.0.1:8443 your_user@your_server_ip
Now open https://localhost:8443 in your browser. The Dashboard uses a self-signed certificate, so accept the browser warning. Paste the token from Step 5 into the Bearer token field and click Sign in.
You should see the workloads overview. Switch namespaces with the selector at the top, open a pod to view its logs, or use the + button to apply a manifest. With the viewer token, write actions fail with a "forbidden" message, which confirms RBAC is working.
Stop the tunnel and the port forward with Ctrl+C when you are done.
ImportantDo not change the Service to
NodePortorLoadBalancer, and do not publish the Dashboard through an Ingress unless it sits behind an additional authentication layer (SSO proxy, VPN or IP allowlist). An exposed login page invites token brute-forcing and phishing.
Troubleshooting
Error: Kubernetes cluster unreachable from Helm. Helm cannot find a kubeconfig. Export KUBECONFIG to the file kubectl uses, or on K3s copy /etc/rancher/k3s/k3s.yaml to ~/.kube/config and fix its ownership.
The login page rejects the token. The token expired or was copied incompletely. Generate a new one and paste it without line breaks.
Pages show "forbidden" errors after login. The account lacks permissions for that resource. This is expected for viewer-user on Secrets or write actions; bind a broader role if the user really needs it.
port-forward exits with "connection refused". The Kong pod is not ready yet. Check kubectl -n kubernetes-dashboard get pods and kubectl -n kubernetes-dashboard logs deploy/kubernetes-dashboard-kong.
Conclusion
You installed the Kubernetes Dashboard with Helm, created an administrator and a read-only account, logged in with short-lived tokens and accessed the UI only through an SSH tunnel. Next, you can create namespace-scoped accounts with RoleBinding for each team, upgrade the Dashboard with the same helm upgrade --install command, or evaluate Headlamp as an actively maintained alternative.
