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.

Prerequisites

To follow this tutorial, you will need:

  • A running Kubernetes cluster and a server where kubectl can reach it with admin rights, for example a CubePath VPS running Ubuntu 24.04 with K3s or MicroK8s. kubectl get nodes should return your nodes as Ready.
  • A non-root user with sudo privileges 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

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.

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.